From 3693eb606f15f60aa5e269a25197b7ddb13a7865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 6 Nov 2023 18:23:50 -0500 Subject: [PATCH 01/19] feat: empty skeleton for dap command --- Cargo.lock | 12 ++++++++++++ tooling/debugger/Cargo.toml | 1 + tooling/nargo_cli/src/cli/dap_cmd.rs | 17 +++++++++++++++++ tooling/nargo_cli/src/cli/mod.rs | 5 +++++ 4 files changed, 35 insertions(+) create mode 100644 tooling/nargo_cli/src/cli/dap_cmd.rs diff --git a/Cargo.lock b/Cargo.lock index 43958d2f1cb..ff7dbd65c8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1209,6 +1209,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "dap" +version = "0.4.1-alpha1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c7fc89d334ab745ba679f94c7314c9b17ecdcd923c111df6206e9fd7729fa9" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "darling" version = "0.20.3" @@ -2522,6 +2533,7 @@ version = "0.19.4" dependencies = [ "acvm", "codespan-reporting", + "dap", "easy-repl", "nargo", "noirc_errors", diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index 53c71754da4..239035ec46e 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -17,3 +17,4 @@ thiserror.workspace = true codespan-reporting.workspace = true easy-repl = "0.2.1" owo-colors = "3" +dap = "0.4.1-alpha1" diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs new file mode 100644 index 00000000000..64452442c9e --- /dev/null +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -0,0 +1,17 @@ +use backend_interface::Backend; +use clap::Args; + +use crate::errors::CliError; + +use super::NargoConfig; + +#[derive(Debug, Clone, Args)] +pub(crate) struct DapCommand; + +pub(crate) fn run( + _backend: &Backend, + _args: DapCommand, + _config: NargoConfig, +) -> Result<(), CliError> { + Ok(()) +} diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index 88c6b57a98c..7e5478dd915 100644 --- a/tooling/nargo_cli/src/cli/mod.rs +++ b/tooling/nargo_cli/src/cli/mod.rs @@ -20,6 +20,7 @@ mod fmt_cmd; mod info_cmd; mod init_cmd; mod lsp_cmd; +mod dap_cmd; mod new_cmd; mod prove_cmd; mod test_cmd; @@ -74,6 +75,8 @@ enum NargoCommand { Test(test_cmd::TestCommand), Info(info_cmd::InfoCommand), Lsp(lsp_cmd::LspCommand), + #[command(hide = true)] + Dap(dap_cmd::DapCommand), } pub(crate) fn start_cli() -> eyre::Result<()> { @@ -91,6 +94,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { | NargoCommand::Init(_) | NargoCommand::Lsp(_) | NargoCommand::Backend(_) + | NargoCommand::Dap(_) ) { config.program_dir = find_package_root(&config.program_dir)?; } @@ -112,6 +116,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { NargoCommand::CodegenVerifier(args) => codegen_verifier_cmd::run(&backend, args, config), NargoCommand::Backend(args) => backend_cmd::run(args), NargoCommand::Lsp(args) => lsp_cmd::run(&backend, args, config), + NargoCommand::Dap(args) => dap_cmd::run(&backend, args, config), NargoCommand::Fmt(args) => fmt_cmd::run(args, config), }?; From 01ad8049432748c7a5b8d44172dde0f55ece655f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 7 Nov 2023 15:35:18 -0500 Subject: [PATCH 02/19] feat: map errors from Dap into CliErrors and start basic server loop --- Cargo.lock | 1 + Cargo.toml | 2 ++ tooling/debugger/Cargo.toml | 2 +- tooling/debugger/src/lib.rs | 35 ++++++++++++++++++++++++++++ tooling/nargo_cli/Cargo.toml | 1 + tooling/nargo_cli/src/cli/dap_cmd.rs | 2 +- tooling/nargo_cli/src/cli/mod.rs | 2 +- tooling/nargo_cli/src/errors.rs | 3 +++ 8 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff7dbd65c8a..25b0ed35be8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2433,6 +2433,7 @@ dependencies = [ "color-eyre", "const_format", "criterion", + "dap", "dirs", "fm", "hex", diff --git a/Cargo.toml b/Cargo.toml index 1a37a4f53e1..540a0cb779c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,8 @@ gloo-utils = { version = "0.1", features = ["serde"] } js-sys = "0.3.62" getrandom = "0.2" +# Debugger +dap = "0.4.1-alpha1" cfg-if = "1.0.0" clap = { version = "4.3.19", features = ["derive"] } diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index 239035ec46e..7e738b6c9b6 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -15,6 +15,6 @@ noirc_printable_type.workspace = true noirc_errors.workspace = true thiserror.workspace = true codespan-reporting.workspace = true +dap.workspace = true easy-repl = "0.2.1" owo-colors = "3" -dap = "0.4.1-alpha1" diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 7c6a9e9f618..fceae4140a4 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -1,9 +1,17 @@ mod context; mod repl; +use std::io::{BufReader, BufWriter}; + use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; +use dap::errors::ServerError; +use dap::prelude::Event; +use dap::requests::Command; +use dap::responses; +use dap::server::Server; +use dap::types; use nargo::artifacts::debug::DebugArtifact; use nargo::NargoError; @@ -16,3 +24,30 @@ pub fn debug_circuit( ) -> Result, NargoError> { repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness) } + +pub fn start_dap_server() -> Result<(), ServerError> { + let output = BufWriter::new(std::io::stdout()); + let input = BufReader::new(std::io::stdin()); + let mut server = Server::new(input, output); + + loop { + let req = match server.poll_request()? { + Some(req) => req, + None => break, + }; + if let Command::Initialize(_) = req.command { + let rsp = req.success(responses::ResponseBody::Initialize(types::Capabilities { + ..Default::default() + })); + + // When you call respond, send_event etc. the message will be wrapped + // in a base message with a appropriate seq number, so you don't have to keep track of that yourself + server.respond(rsp)?; + + server.send_event(Event::Initialized)?; + } else { + eprintln!("ERROR: unhandled command"); + } + } + Ok(()) +} diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index 07298ae25d2..8f2e0cca40c 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -47,6 +47,7 @@ similar-asserts.workspace = true termcolor = "1.1.2" color-eyre = "0.6.2" tokio = { version = "1.0", features = ["io-std"] } +dap.workspace = true # Backends backend-interface = { path = "../backend_interface" } diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 64452442c9e..e14bc9674fe 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -13,5 +13,5 @@ pub(crate) fn run( _args: DapCommand, _config: NargoConfig, ) -> Result<(), CliError> { - Ok(()) + noir_debugger::start_dap_server().map_err(CliError::DapError) } diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index 7e5478dd915..a4a38ff7851 100644 --- a/tooling/nargo_cli/src/cli/mod.rs +++ b/tooling/nargo_cli/src/cli/mod.rs @@ -14,13 +14,13 @@ mod backend_cmd; mod check_cmd; mod codegen_verifier_cmd; mod compile_cmd; +mod dap_cmd; mod debug_cmd; mod execute_cmd; mod fmt_cmd; mod info_cmd; mod init_cmd; mod lsp_cmd; -mod dap_cmd; mod new_cmd; mod prove_cmd; mod test_cmd; diff --git a/tooling/nargo_cli/src/errors.rs b/tooling/nargo_cli/src/errors.rs index 92da74c71d4..4636772231b 100644 --- a/tooling/nargo_cli/src/errors.rs +++ b/tooling/nargo_cli/src/errors.rs @@ -53,6 +53,9 @@ pub(crate) enum CliError { #[error(transparent)] LspError(#[from] async_lsp::Error), + #[error(transparent)] + DapError(#[from] dap::errors::ServerError), + /// Error from Nargo #[error(transparent)] NargoError(#[from] NargoError), From 422d13fa537b5f2148831a5cd9f43ded585a323c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Thu, 9 Nov 2023 19:14:53 -0500 Subject: [PATCH 03/19] WIP --- Cargo.lock | 8 +- Cargo.toml | 1 + tooling/backend_interface/Cargo.toml | 2 +- tooling/debugger/Cargo.toml | 4 + tooling/debugger/src/lib.rs | 144 +++++++++++++++++++++++---- tooling/nargo_cli/Cargo.toml | 2 +- tooling/nargo_cli/src/cli/dap_cmd.rs | 142 +++++++++++++++++++++++++- 7 files changed, 278 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25b0ed35be8..eff9f4146c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,7 +422,7 @@ dependencies = [ ] [[package]] -name = "backend-interface" +name = "backend_interface" version = "0.11.0" dependencies = [ "acvm", @@ -2425,7 +2425,7 @@ dependencies = [ "assert_cmd", "assert_fs", "async-lsp", - "backend-interface", + "backend_interface", "barretenberg_blackbox_solver", "bb_abstraction_leaks", "build-data", @@ -2533,13 +2533,17 @@ name = "noir_debugger" version = "0.19.4" dependencies = [ "acvm", + "backend_interface", "codespan-reporting", "dap", "easy-repl", "nargo", + "nargo_toml", + "noirc_driver", "noirc_errors", "noirc_printable_type", "owo-colors", + "serde_json", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 540a0cb779c..0726061d8d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ noirc_printable_type = { path = "compiler/noirc_printable_type" } noir_wasm = { path = "compiler/wasm" } # Noir tooling workspace dependencies +backend_interface = { path = "tooling/backend_interface" } nargo = { path = "tooling/nargo" } nargo_fmt = { path = "tooling/nargo_fmt" } nargo_cli = { path = "tooling/nargo_cli" } diff --git a/tooling/backend_interface/Cargo.toml b/tooling/backend_interface/Cargo.toml index 14b1609dd4a..324d7a5b159 100644 --- a/tooling/backend_interface/Cargo.toml +++ b/tooling/backend_interface/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "backend-interface" +name = "backend_interface" description = "The definition of the backend CLI interface which Nargo uses for proving/verifying ACIR circuits." version = "0.11.0" authors.workspace = true diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index 7e738b6c9b6..e591877168f 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -10,11 +10,15 @@ license.workspace = true [dependencies] acvm.workspace = true +backend_interface.workspace = true nargo.workspace = true +nargo_toml.workspace = true noirc_printable_type.workspace = true noirc_errors.workspace = true +noirc_driver.workspace = true thiserror.workspace = true codespan-reporting.workspace = true dap.workspace = true easy-repl = "0.2.1" owo-colors = "3" +serde_json.workspace = true \ No newline at end of file diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index fceae4140a4..72bd98a323c 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -1,20 +1,23 @@ mod context; mod repl; -use std::io::{BufReader, BufWriter}; +use std::io::{Read, Write}; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; +use context::DebugContext; use dap::errors::ServerError; +use dap::events::StoppedEventBody; use dap::prelude::Event; use dap::requests::Command; -use dap::responses; +use dap::responses::{ResponseBody, StackTraceResponse, ThreadsResponse}; use dap::server::Server; -use dap::types; +use dap::types::{Source, StackFrame, StoppedEventReason, Thread}; use nargo::artifacts::debug::DebugArtifact; use nargo::NargoError; +use noirc_driver::CompiledProgram; pub fn debug_circuit( blackbox_solver: &B, @@ -25,28 +28,131 @@ pub fn debug_circuit( repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness) } -pub fn start_dap_server() -> Result<(), ServerError> { - let output = BufWriter::new(std::io::stdout()); - let input = BufReader::new(std::io::stdin()); - let mut server = Server::new(input, output); +fn send_stopped_event( + server: &mut Server, + reason: StoppedEventReason, +) -> Result<(), ServerError> { + let description = format!("{:?}", &reason); + server.send_event(Event::Stopped(StoppedEventBody { + reason, + description: Some(description), + thread_id: Some(0), + preserve_focus_hint: Some(false), + text: None, + all_threads_stopped: Some(false), + hit_breakpoint_ids: None, + }))?; + Ok(()) +} + +pub fn loop_initialized( + mut server: Server, + solver: &B, + program: CompiledProgram, + initial_witness: WitnessMap, +) -> Result<(), ServerError> { + let debug_artifact = DebugArtifact { + debug_symbols: vec![program.debug.clone()], + file_map: program.file_map.clone(), + warnings: program.warnings.clone(), + }; + let mut context = + DebugContext::new(solver, &program.circuit, &debug_artifact, initial_witness.clone()); + + if matches!(context.get_current_source_location(), None) { + // FIXME: remove this? + _ = context.next(); + } + + server.send_event(Event::Initialized)?; + send_stopped_event(&mut server, StoppedEventReason::Entry)?; loop { let req = match server.poll_request()? { Some(req) => req, None => break, }; - if let Command::Initialize(_) = req.command { - let rsp = req.success(responses::ResponseBody::Initialize(types::Capabilities { - ..Default::default() - })); - - // When you call respond, send_event etc. the message will be wrapped - // in a base message with a appropriate seq number, so you don't have to keep track of that yourself - server.respond(rsp)?; - - server.send_event(Event::Initialized)?; - } else { - eprintln!("ERROR: unhandled command"); + match req.command { + Command::Disconnect(_) => { + eprintln!("INFO: ending debugging session"); + server.respond(req.ack()?)?; + break; + } + Command::SetBreakpoints(ref args) => { + eprintln!("INFO: Received SetBreakpoints {:?}", args); + // FIXME: return the breakpoints actually set + } + Command::Threads => { + server.respond(req.success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: 0, name: "main".to_string() }], + })))?; + } + Command::StackTrace(_) => { + let source_location = context.get_current_source_location(); + eprintln!("{:?}", source_location); + let frames = match source_location { + None => vec![], + Some(locations) => locations + .iter() + .enumerate() + .map(|(index, location)| { + let line_number = + debug_artifact.location_line_number(*location).unwrap(); + let column_number = + debug_artifact.location_column_number(*location).unwrap(); + StackFrame { + id: index as i64, + name: format!("frame #{index}"), + source: Some(Source { + name: None, + path: debug_artifact.file_map[&location.file] + .path + .to_str() + .map(|s| String::from(s)), + source_reference: None, + presentation_hint: None, + origin: None, + sources: None, + adapter_data: None, + checksums: None, + }), + line: line_number as i64, + column: column_number as i64, + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + } + }) + .collect(), + }; + eprintln!("{:?}", frames); + let total_frames = Some(frames.len() as i64); + server.respond(req.success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames: frames, + total_frames, + })))?; + } + Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { + let result = context.next(); + eprintln!("INFO: stepped with result {result:?}"); + match result { + context::DebugCommandResult::Done => { + server.respond(req.success(ResponseBody::Terminate))?; + break; + } + _ => { + server.respond(req.ack()?)?; + send_stopped_event(&mut server, StoppedEventReason::Step)? + }, + } + } + _ => { + eprintln!("{:?}", req.command); + eprintln!("ERROR: unhandled command"); + } } } Ok(()) diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index 8f2e0cca40c..93df78b16a1 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -50,7 +50,7 @@ tokio = { version = "1.0", features = ["io-std"] } dap.workspace = true # Backends -backend-interface = { path = "../backend_interface" } +backend_interface.workspace = true bb_abstraction_leaks.workspace = true [target.'cfg(not(unix))'.dependencies] diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index e14bc9674fe..314dd5a8005 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -1,6 +1,24 @@ use backend_interface::Backend; use clap::Args; +use nargo::constants::PROVER_INPUT_FILE; +use nargo::workspace::Workspace; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_abi::input_parser::Format; +use noirc_driver::{CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_frontend::graph::CrateName; +use std::io::{BufReader, BufWriter, Read, Write}; +use std::path::Path; + +use dap::errors::ServerError; +use dap::requests::Command; +use dap::responses::ResponseBody; +use dap::server::Server; +use dap::types::Capabilities; +use serde_json::Value; + +use super::compile_cmd::compile_bin_package; +use super::fs::inputs::read_inputs_from_file; use crate::errors::CliError; use super::NargoConfig; @@ -8,10 +26,130 @@ use super::NargoConfig; #[derive(Debug, Clone, Args)] pub(crate) struct DapCommand; +fn find_workspace(project_folder: &str, package: Option<&str>) -> Option { + let Ok(toml_path) = get_package_manifest(Path::new(project_folder)) else { + return None; + }; + let package = package.and_then(|p| serde_json::from_str::(p).ok()); + let selection = package.map_or(PackageSelection::DefaultOrAll, PackageSelection::Selected); + let Ok(workspace) = resolve_workspace_from_toml( + &toml_path, + selection, + Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), + ) else { + return None; + }; + Some(workspace) +} + +fn loop_uninitialized( + mut server: Server, + backend: &Backend, +) -> Result<(), ServerError> { + loop { + let req = match server.poll_request()? { + Some(req) => req, + None => break, + }; + match req.command { + Command::Initialize(_) => { + let rsp = + req.success(ResponseBody::Initialize(Capabilities { ..Default::default() })); + server.respond(rsp)?; + } + + Command::Launch(ref arguments) => { + if let Some(Value::Object(ref data)) = arguments.additional_data { + if let Some(Value::String(ref project_folder)) = data.get("projectFolder") { + let project_folder = project_folder.as_str(); + let package = data.get("package").and_then(|v| v.as_str()); + let prover_name = data + .get("proverName") + .and_then(|v| v.as_str()) + .unwrap_or(PROVER_INPUT_FILE); + + eprintln!("Project folder: {}", project_folder); + eprintln!("Package: {}", package.unwrap_or("(none)")); + eprintln!("Prover name: {}", prover_name); + + let Some(workspace) = find_workspace(project_folder, package) else { + server.respond(req.error("Cannot open workspace"))?; + continue; + }; + let Ok((np_language, opcode_support)) = backend.get_backend_info() else { + server.respond(req.error("Failed to get backend info"))?; + continue; + }; + + let Some(package) = workspace.into_iter().find(|p| p.is_binary()) else { + server.respond(req.error("No matching binary packages found in workspace"))?; + continue; + }; + let Ok(compiled_program) = compile_bin_package( + &workspace, + package, + &CompileOptions::default(), + np_language, + &|opcode| opcode_support.is_opcode_supported(opcode), + ) else { + server.respond(req.error("Failed to compile project"))?; + continue; + }; + let Ok((inputs_map, _)) = read_inputs_from_file( + &package.root_dir, + prover_name, + Format::Toml, + &compiled_program.abi, + ) else { + server.respond(req.error("Failed to read program inputs"))?; + continue; + }; + let Ok(initial_witness) = compiled_program.abi.encode(&inputs_map, None) else { + server.respond(req.error("Failed to encode inputs"))?; + continue; + }; + #[allow(deprecated)] + let blackbox_solver = + barretenberg_blackbox_solver::BarretenbergSolver::new(); + + server.respond(req.ack()?)?; + noir_debugger::loop_initialized( + server, + &blackbox_solver, + compiled_program, + initial_witness, + )?; + break; + } else { + server.respond(req.error("Missing project folder argument"))?; + } + } else { + server.respond(req.error("Missing launch arguments"))?; + } + } + + Command::Disconnect(_) => { + server.respond(req.ack()?)?; + break; + } + + _ => { + eprintln!("ERROR: unhandled command"); + } + } + } + Ok(()) +} + pub(crate) fn run( - _backend: &Backend, + backend: &Backend, _args: DapCommand, _config: NargoConfig, ) -> Result<(), CliError> { - noir_debugger::start_dap_server().map_err(CliError::DapError) + // noir_debugger::start_dap_server(backend).map_err(CliError::DapError); + let output = BufWriter::new(std::io::stdout()); + let input = BufReader::new(std::io::stdin()); + let server = Server::new(input, output); + + loop_uninitialized(server, backend).map_err(CliError::DapError) } From fcf43c1985f318e66eab3e28eeaeea6085fd37c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 14 Nov 2023 17:09:31 -0500 Subject: [PATCH 04/19] WIP: next, continue and disassembly view --- tooling/debugger/src/context.rs | 77 ++++++++++++++++++++++++++ tooling/debugger/src/lib.rs | 83 +++++++++++++++++++++++++--- tooling/nargo_cli/src/cli/dap_cmd.rs | 11 +++- 3 files changed, 161 insertions(+), 10 deletions(-) diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 4c429ca2a67..a384a5f1a4b 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -87,6 +87,83 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .and_then(|location| self.debug_artifact.debug_symbols[0].opcode_location(location)) } + pub(super) fn offset_opcode_location( + &self, + location: &Option, + mut offset: i64, + ) -> Option { + if offset == 0 { + return location.clone(); + } + let Some(location) = location else { + return None; + }; + + let (mut acir_index, mut brillig_index) = match location { + OpcodeLocation::Acir(acir_index) => (*acir_index, 0), + OpcodeLocation::Brillig { acir_index, brillig_index } => (*acir_index, *brillig_index), + }; + let opcodes = self.get_opcodes(); + if offset > 0 { + while offset > 0 { + if let Opcode::Brillig(ref brillig_block) = opcodes[acir_index] { + let delta = (brillig_block.bytecode.len() - brillig_index) as i64; + if offset > delta { + offset -= delta; + acir_index += 1; + brillig_index = 0; + } else { + brillig_index += offset as usize; + break; + } + } else { + offset -= 1; + acir_index += 1; + brillig_index = 0; + } + if acir_index >= opcodes.len() { + return None; + } + } + } else { + while offset < 0 { + if matches!(opcodes[acir_index], Opcode::Brillig(_)) { + if -offset > (brillig_index as i64) { + if acir_index == 0 { + return None; + } + offset = 0; + acir_index -= 1; + if let Opcode::Brillig(ref brillig_block) = opcodes[acir_index] { + brillig_index = brillig_block.bytecode.len() - 1; + } else { + brillig_index = 0; + } + } else { + brillig_index -= offset as usize; + break; + } + } else { + if acir_index == 0 { + return None; + } + offset += 1; + acir_index -= 1; + brillig_index = 0; + } + } + } + if matches!(opcodes[acir_index], Opcode::Brillig(_)) { + Some(OpcodeLocation::Brillig { acir_index, brillig_index }) + } else { + Some(OpcodeLocation::Acir(acir_index)) + } + } + + pub(super) fn render_opcode_at_location(&self, location: &Option) -> String { + String::from("invalid") + } + fn step_brillig_opcode(&mut self) -> DebugCommandResult { let Some(mut solver) = self.brillig_solver.take() else { unreachable!("Missing Brillig solver"); diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 72bd98a323c..48451cd7b79 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -2,7 +2,9 @@ mod context; mod repl; use std::io::{Read, Write}; +use std::str::FromStr; +use acvm::acir::circuit::OpcodeLocation; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -11,9 +13,13 @@ use dap::errors::ServerError; use dap::events::StoppedEventBody; use dap::prelude::Event; use dap::requests::Command; -use dap::responses::{ResponseBody, StackTraceResponse, ThreadsResponse}; +use dap::responses::{ + DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, + SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, + ThreadsResponse, +}; use dap::server::Server; -use dap::types::{Source, StackFrame, StoppedEventReason, Thread}; +use dap::types::{DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread}; use nargo::artifacts::debug::DebugArtifact; use nargo::NargoError; @@ -80,7 +86,22 @@ pub fn loop_initialized( } Command::SetBreakpoints(ref args) => { eprintln!("INFO: Received SetBreakpoints {:?}", args); - // FIXME: return the breakpoints actually set + // FIXME: set and return the breakpoints actually set + server.respond(req.success(ResponseBody::SetBreakpoints( + SetBreakpointsResponse { breakpoints: vec![] }, + )))?; + } + Command::SetExceptionBreakpoints(_) => { + server.respond(req.success(ResponseBody::SetExceptionBreakpoints( + SetExceptionBreakpointsResponse { breakpoints: None }, + )))?; + } + Command::SetInstructionBreakpoints(ref args) => { + eprintln!("INFO: Received SetInstructionBreakpoints {:?}", args); + // FIXME: set and return the breakpoints actually set + server.respond(req.success(ResponseBody::SetInstructionBreakpoints( + SetInstructionBreakpointsResponse { breakpoints: vec![] }, + )))?; } Command::Threads => { server.respond(req.success(ResponseBody::Threads(ThreadsResponse { @@ -88,8 +109,8 @@ pub fn loop_initialized( })))?; } Command::StackTrace(_) => { + let opcode_location = context.get_current_opcode_location(); let source_location = context.get_current_source_location(); - eprintln!("{:?}", source_location); let frames = match source_location { None => vec![], Some(locations) => locations @@ -100,6 +121,7 @@ pub fn loop_initialized( debug_artifact.location_line_number(*location).unwrap(); let column_number = debug_artifact.location_column_number(*location).unwrap(); + let ip_reference = opcode_location.map(|location| location.to_string()); StackFrame { id: index as i64, name: format!("frame #{index}"), @@ -121,20 +143,47 @@ pub fn loop_initialized( end_line: None, end_column: None, can_restart: None, - instruction_pointer_reference: None, + instruction_pointer_reference: ip_reference, module_id: None, presentation_hint: None, } }) .collect(), }; - eprintln!("{:?}", frames); let total_frames = Some(frames.len() as i64); server.respond(req.success(ResponseBody::StackTrace(StackTraceResponse { stack_frames: frames, total_frames, })))?; } + Command::Disassemble(ref args) => { + eprintln!("INFO: Received Disassemble {:?}", args); + let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); + let mut opcode_location = context + .offset_opcode_location(&starting_ip, args.instruction_offset.unwrap_or(0)) + .or(Some(OpcodeLocation::Acir(0))); + eprintln!("INFO: From IP {opcode_location:?}"); + let mut count = args.instruction_count; + let mut instructions: Vec = vec![]; + while count > 0 { + instructions.push(DisassembledInstruction { + address: format!("{}", opcode_location.unwrap_or(OpcodeLocation::Acir(0))), + instruction_bytes: None, + instruction: context.render_opcode_at_location(&opcode_location), + symbol: None, + location: None, + line: None, + column: None, + end_line: None, + end_column: None, + }); + opcode_location = context.offset_opcode_location(&opcode_location, 1); + count -= 1; + } + server.respond( + req.success(ResponseBody::Disassemble(DisassembleResponse { instructions })), + )?; + } Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { let result = context.next(); eprintln!("INFO: stepped with result {result:?}"); @@ -146,9 +195,29 @@ pub fn loop_initialized( _ => { server.respond(req.ack()?)?; send_stopped_event(&mut server, StoppedEventReason::Step)? - }, + } + } + } + Command::Continue(_) => { + let result = context.cont(); + eprintln!("INFO: continue with result {result:?}"); + match result { + context::DebugCommandResult::Done => { + server.respond(req.success(ResponseBody::Terminate))?; + break; + } + _ => { + server.respond(req.ack()?)?; + send_stopped_event(&mut server, StoppedEventReason::Pause)? + } } } + Command::Scopes(_) => { + // FIXME + server.respond( + req.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![] })), + )?; + } _ => { eprintln!("{:?}", req.command); eprintln!("ERROR: unhandled command"); diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 314dd5a8005..ae00eb594cf 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -52,9 +52,14 @@ fn loop_uninitialized( None => break, }; match req.command { - Command::Initialize(_) => { - let rsp = - req.success(ResponseBody::Initialize(Capabilities { ..Default::default() })); + Command::Initialize(ref args) => { + eprintln!("INIT ARGS: {:?}", args); + let rsp = req.success(ResponseBody::Initialize(Capabilities { + supports_disassemble_request: Some(true), + supports_instruction_breakpoints: Some(true), + supports_stepping_granularity: Some(true), + ..Default::default() + })); server.respond(rsp)?; } From cff989eb160b7ea5bb5d280b2c3f4ddd4cf39ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Fri, 17 Nov 2023 18:50:14 -0500 Subject: [PATCH 05/19] feat: rewrite offset_opcode_location and add unit tests --- tooling/debugger/src/context.rs | 611 ++++++++++++++++----------- tooling/debugger/src/lib.rs | 18 +- tooling/nargo_cli/src/cli/dap_cmd.rs | 2 +- 3 files changed, 384 insertions(+), 247 deletions(-) diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index a384a5f1a4b..9fd83c5e358 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -87,80 +87,81 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .and_then(|location| self.debug_artifact.debug_symbols[0].opcode_location(location)) } + fn get_opcodes_sizes(&self) -> Vec { + self.get_opcodes() + .iter() + .map(|opcode| match opcode { + Opcode::Brillig(brillig_block) => brillig_block.bytecode.len(), + _ => 1, + }) + .collect() + } + pub(super) fn offset_opcode_location( &self, location: &Option, mut offset: i64, - ) -> Option { + ) -> (Option, i64) { if offset == 0 { - return location.clone(); + return (location.clone(), 0); } let Some(location) = location else { - return None; + return (None, offset); }; let (mut acir_index, mut brillig_index) = match location { OpcodeLocation::Acir(acir_index) => (*acir_index, 0), OpcodeLocation::Brillig { acir_index, brillig_index } => (*acir_index, *brillig_index), }; - let opcodes = self.get_opcodes(); + let opcode_sizes = self.get_opcodes_sizes(); if offset > 0 { while offset > 0 { - if let Opcode::Brillig(ref brillig_block) = opcodes[acir_index] { - let delta = (brillig_block.bytecode.len() - brillig_index) as i64; - if offset > delta { - offset -= delta; - acir_index += 1; - brillig_index = 0; - } else { - brillig_index += offset as usize; - break; - } - } else { - offset -= 1; + let opcode_size = opcode_sizes[acir_index] as i64 - brillig_index as i64; + if offset >= opcode_size { acir_index += 1; + offset -= opcode_size; brillig_index = 0; + } else { + brillig_index += offset as usize; + offset = 0; } - if acir_index >= opcodes.len() { - return None; + if acir_index >= opcode_sizes.len() { + return (None, offset); } } } else { while offset < 0 { - if matches!(opcodes[acir_index], Opcode::Brillig(_)) { - if -offset > (brillig_index as i64) { - if acir_index == 0 { - return None; - } + if brillig_index > 0 { + if brillig_index > (-offset) as usize { + brillig_index -= (-offset) as usize; offset = 0; - acir_index -= 1; - if let Opcode::Brillig(ref brillig_block) = opcodes[acir_index] { - brillig_index = brillig_block.bytecode.len() - 1; - } else { - brillig_index = 0; - } } else { - brillig_index -= offset as usize; - break; + offset += brillig_index as i64; + brillig_index = 0; } } else { if acir_index == 0 { - return None; + return (None, offset); } - offset += 1; acir_index -= 1; - brillig_index = 0; + let opcode_size = opcode_sizes[acir_index] as i64; + if opcode_size <= -offset { + offset += opcode_size; + } else { + brillig_index = (opcode_size + offset) as usize; + offset = 0; + } } } } - if matches!(opcodes[acir_index], Opcode::Brillig(_)) { - Some(OpcodeLocation::Brillig { acir_index, brillig_index }) + if brillig_index > 0 { + (Some(OpcodeLocation::Brillig { acir_index, brillig_index }), 0) } else { - Some(OpcodeLocation::Acir(acir_index)) + (Some(OpcodeLocation::Acir(acir_index)), 0) } } - pub(super) fn render_opcode_at_location(&self, location: &Option) -> String { + pub(super) fn render_opcode_at_location(&self, _location: &Option) -> String { String::from("invalid") } @@ -398,221 +399,351 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } #[cfg(test)] -struct StubbedSolver; +mod tests { + use std::collections::BTreeMap; -#[cfg(test)] -impl BlackBoxFunctionSolver for StubbedSolver { - fn schnorr_verify( - &self, - _public_key_x: &FieldElement, - _public_key_y: &FieldElement, - _signature: &[u8], - _message: &[u8], - ) -> Result { - unimplemented!(); - } + use acvm::{ + acir::{ + circuit::{brillig::Brillig, opcodes::BlockId, Circuit, Opcode, OpcodeLocation}, + native_types::{Expression, Witness, WitnessMap}, + }, + brillig_vm::brillig::{Opcode as BrilligOpcode, Value}, + BlackBoxFunctionSolver, FieldElement, + }; + use nargo::{artifacts::debug::DebugArtifact, ops::DefaultForeignCallExecutor}; - fn pedersen_commitment( - &self, - _inputs: &[FieldElement], - _domain_separator: u32, - ) -> Result<(FieldElement, FieldElement), acvm::BlackBoxResolutionError> { - unimplemented!(); - } + use super::{DebugCommandResult, DebugContext}; - fn pedersen_hash( - &self, - _inputs: &[FieldElement], - _domain_separator: u32, - ) -> Result { - unimplemented!(); - } + struct StubbedSolver; - fn fixed_base_scalar_mul( - &self, - _low: &FieldElement, - _high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), acvm::BlackBoxResolutionError> { - unimplemented!(); - } -} + impl BlackBoxFunctionSolver for StubbedSolver { + fn schnorr_verify( + &self, + _public_key_x: &acvm::FieldElement, + _public_key_y: &acvm::FieldElement, + _signature: &[u8], + _message: &[u8], + ) -> Result { + unimplemented!(); + } -#[cfg(test)] -#[test] -fn test_resolve_foreign_calls_stepping_into_brillig() { - use std::collections::BTreeMap; + fn pedersen_commitment( + &self, + _inputs: &[acvm::FieldElement], + _domain_separator: u32, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> + { + unimplemented!(); + } - use acvm::acir::{ - brillig::{Opcode as BrilligOpcode, RegisterIndex, RegisterOrMemory}, - circuit::brillig::{Brillig, BrilligInputs}, - native_types::Expression, - }; + fn pedersen_hash( + &self, + _inputs: &[acvm::FieldElement], + _domain_separator: u32, + ) -> Result { + unimplemented!(); + } - use nargo::ops::DefaultForeignCallExecutor; - - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - - let blackbox_solver = &StubbedSolver; - - let brillig_opcodes = Brillig { - inputs: vec![BrilligInputs::Single(Expression { - linear_combinations: vec![(fe_1, w_x)], - ..Expression::default() - })], - outputs: vec![], - bytecode: vec![ - BrilligOpcode::Const { destination: RegisterIndex::from(1), value: Value::from(fe_0) }, - BrilligOpcode::ForeignCall { - function: "clear_mock".into(), - destinations: vec![], - inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], - }, - BrilligOpcode::Stop, - ], - predicate: None, - }; - let opcodes = vec![Opcode::Brillig(brillig_opcodes)]; - let current_witness_index = 2; - let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; - - let debug_symbols = vec![]; - let file_map = BTreeMap::new(); - let warnings = vec![]; - let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; - - let initial_witness = BTreeMap::from([(Witness(1), fe_1)]).into(); - - let mut context = DebugContext::new( - blackbox_solver, - circuit, - debug_artifact, - initial_witness, - Box::new(DefaultForeignCallExecutor::new(true)), - ); - - assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(0))); - - // execute the first Brillig opcode (const) - let result = context.step_into_opcode(); - assert!(matches!(result, DebugCommandResult::Ok)); - assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) - ); - - // try to execute the second Brillig opcode (and resolve the foreign call) - let result = context.step_into_opcode(); - assert!(matches!(result, DebugCommandResult::Ok)); - assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) - ); - - // retry the second Brillig opcode (foreign call should be finished) - let result = context.step_into_opcode(); - assert!(matches!(result, DebugCommandResult::Ok)); - assert_eq!( - context.get_current_opcode_location(), - Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }) - ); - - // last Brillig opcode - let result = context.step_into_opcode(); - assert!(matches!(result, DebugCommandResult::Done)); - assert_eq!(context.get_current_opcode_location(), None); -} + fn fixed_base_scalar_mul( + &self, + _low: &acvm::FieldElement, + _high: &acvm::FieldElement, + ) -> Result<(acvm::FieldElement, acvm::FieldElement), acvm::BlackBoxResolutionError> + { + unimplemented!(); + } + } -#[cfg(test)] -#[test] -fn test_break_brillig_block_while_stepping_acir_opcodes() { - use std::collections::BTreeMap; + #[test] + fn test_offset_opcode_location() { + let blackbox_solver = &StubbedSolver; + let opcodes = vec![ + Opcode::Brillig(Brillig { + inputs: vec![], + outputs: vec![], + bytecode: vec![BrilligOpcode::Stop, BrilligOpcode::Stop, BrilligOpcode::Stop], + predicate: None, + }), + Opcode::MemoryInit { block_id: BlockId(0), init: vec![] }, + Opcode::Brillig(Brillig { + inputs: vec![], + outputs: vec![], + bytecode: vec![BrilligOpcode::Stop, BrilligOpcode::Stop, BrilligOpcode::Stop], + predicate: None, + }), + Opcode::Arithmetic(Expression::default()), + ]; + let circuit = Circuit { opcodes, ..Circuit::default() }; + let debug_artifact = + DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new(), warnings: vec![] }; + let context = DebugContext::new( + blackbox_solver, + &circuit, + &debug_artifact, + WitnessMap::new(), + Box::new(DefaultForeignCallExecutor::new(true)), + ); + + assert_eq!(context.offset_opcode_location(&None, 0), (None, 0)); + assert_eq!(context.offset_opcode_location(&None, 2), (None, 2)); + assert_eq!(context.offset_opcode_location(&None, -2), (None, -2)); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 0), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 1), + (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 2), + (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 3), + (Some(OpcodeLocation::Acir(1)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 4), + (Some(OpcodeLocation::Acir(2)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 5), + (Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 7), + (Some(OpcodeLocation::Acir(3)), 0) + ); + assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 8), (None, 0)); + assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), 20), (None, 12)); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(1)), 2), + (Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), 0) + ); + assert_eq!(context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), -1), (None, -1)); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(0)), -10), + (None, -10) + ); + + assert_eq!( + context.offset_opcode_location( + &Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }), + -1 + ), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location( + &Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), + -2 + ), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(1)), -3), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(2)), -4), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location( + &Some(OpcodeLocation::Brillig { acir_index: 2, brillig_index: 1 }), + -5 + ), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(3)), -7), + (Some(OpcodeLocation::Acir(0)), 0) + ); + assert_eq!( + context.offset_opcode_location(&Some(OpcodeLocation::Acir(2)), -2), + (Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }), 0) + ); + } + + #[test] + fn test_resolve_foreign_calls_stepping_into_brillig() { + use std::collections::BTreeMap; + + use acvm::acir::{ + brillig::{Opcode as BrilligOpcode, RegisterIndex, RegisterOrMemory}, + circuit::brillig::{Brillig, BrilligInputs}, + native_types::Expression, + }; - use acvm::acir::{ - brillig::{Opcode as BrilligOpcode, RegisterIndex}, - circuit::brillig::{Brillig, BrilligInputs, BrilligOutputs}, - native_types::Expression, - }; - use acvm::brillig_vm::brillig::BinaryFieldOp; - use nargo::ops::DefaultForeignCallExecutor; + use nargo::ops::DefaultForeignCallExecutor; - let fe_0 = FieldElement::zero(); - let fe_1 = FieldElement::one(); - let w_x = Witness(1); - let w_y = Witness(2); - let w_z = Witness(3); + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); - let blackbox_solver = &StubbedSolver; + let blackbox_solver = &StubbedSolver; - // This Brillig block is equivalent to: z = x + y - let brillig_opcodes = Brillig { - inputs: vec![ - BrilligInputs::Single(Expression { + let brillig_opcodes = Brillig { + inputs: vec![BrilligInputs::Single(Expression { linear_combinations: vec![(fe_1, w_x)], ..Expression::default() + })], + outputs: vec![], + bytecode: vec![ + BrilligOpcode::Const { + destination: RegisterIndex::from(1), + value: Value::from(fe_0), + }, + BrilligOpcode::ForeignCall { + function: "clear_mock".into(), + destinations: vec![], + inputs: vec![RegisterOrMemory::RegisterIndex(RegisterIndex::from(0))], + }, + BrilligOpcode::Stop, + ], + predicate: None, + }; + let opcodes = vec![Opcode::Brillig(brillig_opcodes)]; + let current_witness_index = 2; + let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + + let debug_symbols = vec![]; + let file_map = BTreeMap::new(); + let warnings = vec![]; + let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; + + let initial_witness = BTreeMap::from([(Witness(1), fe_1)]).into(); + + let mut context = DebugContext::new( + blackbox_solver, + circuit, + debug_artifact, + initial_witness, + Box::new(DefaultForeignCallExecutor::new(true)), + ); + + assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(0))); + + // execute the first Brillig opcode (const) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) + ); + + // try to execute the second Brillig opcode (and resolve the foreign call) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }) + ); + + // retry the second Brillig opcode (foreign call should be finished) + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!( + context.get_current_opcode_location(), + Some(OpcodeLocation::Brillig { acir_index: 0, brillig_index: 2 }) + ); + + // last Brillig opcode + let result = context.step_into_opcode(); + assert!(matches!(result, DebugCommandResult::Done)); + assert_eq!(context.get_current_opcode_location(), None); + } + + #[test] + fn test_break_brillig_block_while_stepping_acir_opcodes() { + use std::collections::BTreeMap; + + use acvm::acir::{ + brillig::{Opcode as BrilligOpcode, RegisterIndex}, + circuit::brillig::{Brillig, BrilligInputs, BrilligOutputs}, + native_types::Expression, + }; + use acvm::brillig_vm::brillig::BinaryFieldOp; + use nargo::ops::DefaultForeignCallExecutor; + + let fe_0 = FieldElement::zero(); + let fe_1 = FieldElement::one(); + let w_x = Witness(1); + let w_y = Witness(2); + let w_z = Witness(3); + + let blackbox_solver = &StubbedSolver; + + // This Brillig block is equivalent to: z = x + y + let brillig_opcodes = Brillig { + inputs: vec![ + BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_x)], + ..Expression::default() + }), + BrilligInputs::Single(Expression { + linear_combinations: vec![(fe_1, w_y)], + ..Expression::default() + }), + ], + outputs: vec![BrilligOutputs::Simple(w_z)], + bytecode: vec![ + BrilligOpcode::BinaryFieldOp { + destination: RegisterIndex::from(0), + op: BinaryFieldOp::Add, + lhs: RegisterIndex::from(0), + rhs: RegisterIndex::from(1), + }, + BrilligOpcode::Stop, + ], + predicate: None, + }; + let opcodes = vec![ + // z = x + y + Opcode::Brillig(brillig_opcodes), + // x + y - z = 0 + Opcode::Arithmetic(Expression { + mul_terms: vec![], + linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], + q_c: fe_0, }), - BrilligInputs::Single(Expression { - linear_combinations: vec![(fe_1, w_y)], - ..Expression::default() - }), - ], - outputs: vec![BrilligOutputs::Simple(w_z)], - bytecode: vec![ - BrilligOpcode::BinaryFieldOp { - destination: RegisterIndex::from(0), - op: BinaryFieldOp::Add, - lhs: RegisterIndex::from(0), - rhs: RegisterIndex::from(1), - }, - BrilligOpcode::Stop, - ], - predicate: None, - }; - let opcodes = vec![ - // z = x + y - Opcode::Brillig(brillig_opcodes), - // x + y - z = 0 - Opcode::Arithmetic(Expression { - mul_terms: vec![], - linear_combinations: vec![(fe_1, w_x), (fe_1, w_y), (-fe_1, w_z)], - q_c: fe_0, - }), - ]; - let current_witness_index = 3; - let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; - - let debug_symbols = vec![]; - let file_map = BTreeMap::new(); - let warnings = vec![]; - let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; - - let initial_witness = BTreeMap::from([(Witness(1), fe_1), (Witness(2), fe_1)]).into(); - - let mut context = DebugContext::new( - blackbox_solver, - circuit, - debug_artifact, - initial_witness, - Box::new(DefaultForeignCallExecutor::new(true)), - ); - - // set breakpoint - let breakpoint_location = OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }; - assert!(context.add_breakpoint(breakpoint_location)); - - // execute the first ACIR opcode (Brillig block) -> should reach the breakpoint instead - let result = context.step_acir_opcode(); - assert!(matches!(result, DebugCommandResult::BreakpointReached(_))); - assert_eq!(context.get_current_opcode_location(), Some(breakpoint_location)); - - // continue execution to the next ACIR opcode - let result = context.step_acir_opcode(); - assert!(matches!(result, DebugCommandResult::Ok)); - assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(1))); - - // last ACIR opcode - let result = context.step_acir_opcode(); - assert!(matches!(result, DebugCommandResult::Done)); - assert_eq!(context.get_current_opcode_location(), None); + ]; + let current_witness_index = 3; + let circuit = &Circuit { current_witness_index, opcodes, ..Circuit::default() }; + + let debug_symbols = vec![]; + let file_map = BTreeMap::new(); + let warnings = vec![]; + let debug_artifact = &DebugArtifact { debug_symbols, file_map, warnings }; + + let initial_witness = BTreeMap::from([(Witness(1), fe_1), (Witness(2), fe_1)]).into(); + + let mut context = DebugContext::new( + blackbox_solver, + circuit, + debug_artifact, + initial_witness, + Box::new(DefaultForeignCallExecutor::new(true)), + ); + + // set breakpoint + let breakpoint_location = OpcodeLocation::Brillig { acir_index: 0, brillig_index: 1 }; + assert!(context.add_breakpoint(breakpoint_location)); + + // execute the first ACIR opcode (Brillig block) -> should reach the breakpoint instead + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::BreakpointReached(_))); + assert_eq!(context.get_current_opcode_location(), Some(breakpoint_location)); + + // continue execution to the next ACIR opcode + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::Ok)); + assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(1))); + + // last ACIR opcode + let result = context.step_acir_opcode(); + assert!(matches!(result, DebugCommandResult::Done)); + assert_eq!(context.get_current_opcode_location(), None); + } } diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 48451cd7b79..148a4b16c96 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -22,6 +22,7 @@ use dap::server::Server; use dap::types::{DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread}; use nargo::artifacts::debug::DebugArtifact; +use nargo::ops::DefaultForeignCallExecutor; use nargo::NargoError; use noirc_driver::CompiledProgram; @@ -62,8 +63,13 @@ pub fn loop_initialized( file_map: program.file_map.clone(), warnings: program.warnings.clone(), }; - let mut context = - DebugContext::new(solver, &program.circuit, &debug_artifact, initial_witness.clone()); + let mut context = DebugContext::new( + solver, + &program.circuit, + &debug_artifact, + initial_witness.clone(), + Box::new(DefaultForeignCallExecutor::new(true)), + ); if matches!(context.get_current_source_location(), None) { // FIXME: remove this? @@ -159,9 +165,9 @@ pub fn loop_initialized( Command::Disassemble(ref args) => { eprintln!("INFO: Received Disassemble {:?}", args); let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); - let mut opcode_location = context - .offset_opcode_location(&starting_ip, args.instruction_offset.unwrap_or(0)) - .or(Some(OpcodeLocation::Acir(0))); + let (opcode_location, _) = context + .offset_opcode_location(&starting_ip, args.instruction_offset.unwrap_or(0)); + let mut opcode_location = opcode_location.or(Some(OpcodeLocation::Acir(0))); eprintln!("INFO: From IP {opcode_location:?}"); let mut count = args.instruction_count; let mut instructions: Vec = vec![]; @@ -177,7 +183,7 @@ pub fn loop_initialized( end_line: None, end_column: None, }); - opcode_location = context.offset_opcode_location(&opcode_location, 1); + (opcode_location, _) = context.offset_opcode_location(&opcode_location, 1); count -= 1; } server.respond( diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index ae00eb594cf..116b87e3789 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -95,7 +95,7 @@ fn loop_uninitialized( package, &CompileOptions::default(), np_language, - &|opcode| opcode_support.is_opcode_supported(opcode), + &opcode_support, ) else { server.respond(req.error("Failed to compile project"))?; continue; From e1da132c925e94d66254b4a0a07c732238df5051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Sat, 18 Nov 2023 12:04:56 -0500 Subject: [PATCH 06/19] feat: render the circuit and Brillig opcodes for disassembled view --- tooling/debugger/src/context.rs | 31 ++++++++++++++-- tooling/debugger/src/lib.rs | 65 +++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 9fd83c5e358..a25bb203752 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -97,13 +97,18 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { .collect() } + /// Offsets the given location by the given number of opcodes (including + /// Brillig opcodes). If the offset would move the location outside of a + /// valid circuit location, returns None and the number of remaining + /// opcodes/instructions left which span outside the valid range in the + /// second element of the returned tuple. pub(super) fn offset_opcode_location( &self, location: &Option, mut offset: i64, ) -> (Option, i64) { if offset == 0 { - return (location.clone(), 0); + return (*location, 0); } let Some(location) = location else { return (None, offset); @@ -161,8 +166,28 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - pub(super) fn render_opcode_at_location(&self, _location: &Option) -> String { - String::from("invalid") + pub(super) fn render_opcode_at_location(&self, location: &Option) -> String { + let opcodes = self.get_opcodes(); + match location { + None => String::from("invalid"), + Some(OpcodeLocation::Acir(acir_index)) => { + let opcode = &opcodes[*acir_index]; + if let Opcode::Brillig(ref brillig) = opcode { + let first_opcode = &brillig.bytecode[0]; + format!("BRILLIG {first_opcode:?}") + } else { + format!("{opcode:?}") + } + } + Some(OpcodeLocation::Brillig { acir_index, brillig_index }) => { + if let Opcode::Brillig(ref brillig) = opcodes[*acir_index] { + let opcode = &brillig.bytecode[*brillig_index]; + format!(" | {opcode:?}") + } else { + String::from(" | invalid") + } + } + } } fn step_brillig_opcode(&mut self) -> DebugCommandResult { diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 148a4b16c96..3c02d8370fa 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -132,26 +132,16 @@ pub fn loop_initialized( id: index as i64, name: format!("frame #{index}"), source: Some(Source { - name: None, path: debug_artifact.file_map[&location.file] .path .to_str() - .map(|s| String::from(s)), - source_reference: None, - presentation_hint: None, - origin: None, - sources: None, - adapter_data: None, - checksums: None, + .map(String::from), + ..Source::default() }), line: line_number as i64, column: column_number as i64, - end_line: None, - end_column: None, - can_restart: None, instruction_pointer_reference: ip_reference, - module_id: None, - presentation_hint: None, + ..StackFrame::default() } }) .collect(), @@ -163,29 +153,50 @@ pub fn loop_initialized( })))?; } Command::Disassemble(ref args) => { - eprintln!("INFO: Received Disassemble {:?}", args); let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); - let (opcode_location, _) = context - .offset_opcode_location(&starting_ip, args.instruction_offset.unwrap_or(0)); - let mut opcode_location = opcode_location.or(Some(OpcodeLocation::Acir(0))); - eprintln!("INFO: From IP {opcode_location:?}"); + let instruction_offset = args.instruction_offset.unwrap_or(0); + let (mut opcode_location, mut invalid_count) = + context.offset_opcode_location(&starting_ip, instruction_offset); let mut count = args.instruction_count; + let mut instructions: Vec = vec![]; - while count > 0 { + + // leading invalid locations (when the request goes back beyond the start of the program) + if invalid_count < 0 { + while invalid_count < 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count += 1; + count -= 1; + } + if count > 0 { + opcode_location = Some(OpcodeLocation::Acir(0)); + } + } + // the actual opcodes + while count > 0 && !matches!(opcode_location, None) { instructions.push(DisassembledInstruction { - address: format!("{}", opcode_location.unwrap_or(OpcodeLocation::Acir(0))), - instruction_bytes: None, + address: format!("{}", opcode_location.unwrap()), instruction: context.render_opcode_at_location(&opcode_location), - symbol: None, - location: None, - line: None, - column: None, - end_line: None, - end_column: None, + ..DisassembledInstruction::default() }); (opcode_location, _) = context.offset_opcode_location(&opcode_location, 1); count -= 1; } + // any remaining instruction count is beyond the valid opcode vector so return invalid placeholders + while count > 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count -= 1; + count -= 1; + } + server.respond( req.success(ResponseBody::Disassemble(DisassembleResponse { instructions })), )?; From 5a1877c5d52b219c01ad041375ffc70cc05727c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Sat, 18 Nov 2023 15:54:18 -0500 Subject: [PATCH 07/19] chore: refactor dap_cmd for readability --- tooling/debugger/src/lib.rs | 2 +- tooling/nargo_cli/src/cli/dap_cmd.rs | 140 +++++++++++++++------------ 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 3c02d8370fa..97894d3cc1e 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -52,7 +52,7 @@ fn send_stopped_event( Ok(()) } -pub fn loop_initialized( +pub fn run_dap_loop( mut server: Server, solver: &B, program: CompiledProgram, diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 116b87e3789..26a6f3cc4da 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -1,10 +1,11 @@ +use acvm::acir::native_types::WitnessMap; use backend_interface::Backend; use clap::Args; use nargo::constants::PROVER_INPUT_FILE; use nargo::workspace::Workspace; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::Format; -use noirc_driver::{CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_driver::{CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING}; use noirc_frontend::graph::CrateName; use std::io::{BufReader, BufWriter, Read, Write}; @@ -26,6 +27,8 @@ use super::NargoConfig; #[derive(Debug, Clone, Args)] pub(crate) struct DapCommand; +struct LoadError(&'static str); + fn find_workspace(project_folder: &str, package: Option<&str>) -> Option { let Ok(toml_path) = get_package_manifest(Path::new(project_folder)) else { return None; @@ -42,7 +45,47 @@ fn find_workspace(project_folder: &str, package: Option<&str>) -> Option( +fn load_and_compile_project( + backend: &Backend, + project_folder: &str, + package: Option<&str>, + prover_name: &str, +) -> Result<(CompiledProgram, WitnessMap), LoadError> { + let Some(workspace) = find_workspace(project_folder, package) else { + return Err(LoadError("Cannot open workspace")); + }; + let Ok((np_language, opcode_support)) = backend.get_backend_info() else { + return Err(LoadError("Failed to get backend info")); + }; + let Some(package) = workspace.into_iter().find(|p| p.is_binary()) else { + return Err(LoadError("No matching binary packages found in workspace")); + }; + + let Ok(compiled_program) = compile_bin_package( + &workspace, + package, + &CompileOptions::default(), + np_language, + &opcode_support, + ) else { + return Err(LoadError("Failed to compile project")); + }; + let Ok((inputs_map, _)) = read_inputs_from_file( + &package.root_dir, + prover_name, + Format::Toml, + &compiled_program.abi, + ) else { + return Err(LoadError("Failed to read program inputs")); + }; + let Ok(initial_witness) = compiled_program.abi.encode(&inputs_map, None) else { + return Err(LoadError("Failed to encode inputs")); + }; + + Ok((compiled_program, initial_witness)) +} + +fn loop_uninitialized_dap( mut server: Server, backend: &Backend, ) -> Result<(), ServerError> { @@ -51,9 +94,9 @@ fn loop_uninitialized( Some(req) => req, None => break, }; + match req.command { - Command::Initialize(ref args) => { - eprintln!("INIT ARGS: {:?}", args); + Command::Initialize(_) => { let rsp = req.success(ResponseBody::Initialize(Capabilities { supports_disassemble_request: Some(true), supports_instruction_breakpoints: Some(true), @@ -64,72 +107,45 @@ fn loop_uninitialized( } Command::Launch(ref arguments) => { - if let Some(Value::Object(ref data)) = arguments.additional_data { - if let Some(Value::String(ref project_folder)) = data.get("projectFolder") { - let project_folder = project_folder.as_str(); - let package = data.get("package").and_then(|v| v.as_str()); - let prover_name = data - .get("proverName") - .and_then(|v| v.as_str()) - .unwrap_or(PROVER_INPUT_FILE); - - eprintln!("Project folder: {}", project_folder); - eprintln!("Package: {}", package.unwrap_or("(none)")); - eprintln!("Prover name: {}", prover_name); - - let Some(workspace) = find_workspace(project_folder, package) else { - server.respond(req.error("Cannot open workspace"))?; - continue; - }; - let Ok((np_language, opcode_support)) = backend.get_backend_info() else { - server.respond(req.error("Failed to get backend info"))?; - continue; - }; - - let Some(package) = workspace.into_iter().find(|p| p.is_binary()) else { - server.respond(req.error("No matching binary packages found in workspace"))?; - continue; - }; - let Ok(compiled_program) = compile_bin_package( - &workspace, - package, - &CompileOptions::default(), - np_language, - &opcode_support, - ) else { - server.respond(req.error("Failed to compile project"))?; - continue; - }; - let Ok((inputs_map, _)) = read_inputs_from_file( - &package.root_dir, - prover_name, - Format::Toml, - &compiled_program.abi, - ) else { - server.respond(req.error("Failed to read program inputs"))?; - continue; - }; - let Ok(initial_witness) = compiled_program.abi.encode(&inputs_map, None) else { - server.respond(req.error("Failed to encode inputs"))?; - continue; - }; + let Some(Value::Object(ref additional_data)) = arguments.additional_data else { + server.respond(req.error("Missing launch arguments"))?; + continue; + }; + let Some(Value::String(ref project_folder)) = additional_data.get("projectFolder") else { + server.respond(req.error("Missing project folder argument"))?; + continue; + }; + + let project_folder = project_folder.as_str(); + let package = additional_data.get("package").and_then(|v| v.as_str()); + let prover_name = additional_data + .get("proverName") + .and_then(|v| v.as_str()) + .unwrap_or(PROVER_INPUT_FILE); + + eprintln!("Project folder: {}", project_folder); + eprintln!("Package: {}", package.unwrap_or("(default)")); + eprintln!("Prover name: {}", prover_name); + + match load_and_compile_project(backend, project_folder, package, prover_name) { + Ok((compiled_program, initial_witness)) => { + server.respond(req.ack()?)?; + #[allow(deprecated)] let blackbox_solver = barretenberg_blackbox_solver::BarretenbergSolver::new(); - server.respond(req.ack()?)?; - noir_debugger::loop_initialized( + noir_debugger::run_dap_loop( server, &blackbox_solver, compiled_program, initial_witness, )?; break; - } else { - server.respond(req.error("Missing project folder argument"))?; } - } else { - server.respond(req.error("Missing launch arguments"))?; + Err(LoadError(message)) => { + server.respond(req.error(message))?; + } } } @@ -139,7 +155,8 @@ fn loop_uninitialized( } _ => { - eprintln!("ERROR: unhandled command"); + let command = req.command; + eprintln!("ERROR: unhandled command: {command:?}"); } } } @@ -151,10 +168,9 @@ pub(crate) fn run( _args: DapCommand, _config: NargoConfig, ) -> Result<(), CliError> { - // noir_debugger::start_dap_server(backend).map_err(CliError::DapError); let output = BufWriter::new(std::io::stdout()); let input = BufReader::new(std::io::stdin()); let server = Server::new(input, output); - loop_uninitialized(server, backend).map_err(CliError::DapError) + loop_uninitialized_dap(server, backend).map_err(CliError::DapError) } From e3f3d4fb654dcc9d5b4b7c1f1783ff8b9ab5f39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Sat, 18 Nov 2023 17:00:48 -0500 Subject: [PATCH 08/19] chore: refactor, move DAP implementation into a new file --- tooling/debugger/src/dap.rs | 268 ++++++++++++++++++++++++++++++++++++ tooling/debugger/src/lib.rs | 222 +---------------------------- 2 files changed, 273 insertions(+), 217 deletions(-) create mode 100644 tooling/debugger/src/dap.rs diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs new file mode 100644 index 00000000000..5271064bfc9 --- /dev/null +++ b/tooling/debugger/src/dap.rs @@ -0,0 +1,268 @@ +use std::io::{Read, Write}; +use std::str::FromStr; + +use acvm::acir::circuit::{Circuit, OpcodeLocation}; +use acvm::acir::native_types::WitnessMap; +use acvm::BlackBoxFunctionSolver; + +use crate::context::DebugCommandResult; +use crate::context::DebugContext; + +use dap::errors::ServerError; +use dap::events::StoppedEventBody; +use dap::prelude::Event; +use dap::requests::{Command, Request}; +use dap::responses::{ + DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, + SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, + ThreadsResponse, +}; +use dap::server::Server; +use dap::types::{DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread}; +use nargo::artifacts::debug::DebugArtifact; +use nargo::ops::DefaultForeignCallExecutor; + +use noirc_driver::CompiledProgram; + +pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> { + server: Server, + context: DebugContext<'a, B>, + debug_artifact: &'a DebugArtifact, + running: bool, +} + +impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { + pub fn new( + server: Server, + solver: &'a B, + circuit: &'a Circuit, + debug_artifact: &'a DebugArtifact, + initial_witness: WitnessMap, + ) -> Self { + let context = DebugContext::new( + solver, + circuit, + debug_artifact, + initial_witness, + Box::new(DefaultForeignCallExecutor::new(true)), + ); + Self { server, context, debug_artifact, running: false } + } + + fn send_stopped_event(&mut self, reason: StoppedEventReason) -> Result<(), ServerError> { + let description = format!("{:?}", &reason); + self.server.send_event(Event::Stopped(StoppedEventBody { + reason, + description: Some(description), + thread_id: Some(0), + preserve_focus_hint: Some(false), + text: None, + all_threads_stopped: Some(false), + hit_breakpoint_ids: None, + }))?; + Ok(()) + } + + pub fn run_loop(&mut self) -> Result<(), ServerError> { + self.running = true; + + if matches!(self.context.get_current_source_location(), None) { + // FIXME: remove this? + _ = self.context.next(); + } + + self.server.send_event(Event::Initialized)?; + self.send_stopped_event(StoppedEventReason::Entry)?; + + while self.running { + let req = match self.server.poll_request()? { + Some(req) => req, + None => break, + }; + match req.command { + Command::Disconnect(_) => { + eprintln!("INFO: ending debugging session"); + self.server.respond(req.ack()?)?; + break; + } + Command::SetBreakpoints(ref args) => { + eprintln!("INFO: Received SetBreakpoints {:?}", args); + // FIXME: set and return the breakpoints actually set + self.server.respond(req.success(ResponseBody::SetBreakpoints( + SetBreakpointsResponse { breakpoints: vec![] }, + )))?; + } + Command::SetExceptionBreakpoints(_) => { + self.server.respond(req.success(ResponseBody::SetExceptionBreakpoints( + SetExceptionBreakpointsResponse { breakpoints: None }, + )))?; + } + Command::SetInstructionBreakpoints(ref args) => { + eprintln!("INFO: Received SetInstructionBreakpoints {:?}", args); + // FIXME: set and return the breakpoints actually set + self.server.respond(req.success(ResponseBody::SetInstructionBreakpoints( + SetInstructionBreakpointsResponse { breakpoints: vec![] }, + )))?; + } + Command::Threads => { + self.server.respond(req.success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: 0, name: "main".to_string() }], + })))?; + } + Command::StackTrace(_) => { + let opcode_location = self.context.get_current_opcode_location(); + let source_location = self.context.get_current_source_location(); + let frames = match source_location { + None => vec![], + Some(locations) => locations + .iter() + .enumerate() + .map(|(index, location)| { + let line_number = + self.debug_artifact.location_line_number(*location).unwrap(); + let column_number = + self.debug_artifact.location_column_number(*location).unwrap(); + let ip_reference = + opcode_location.map(|location| location.to_string()); + StackFrame { + id: index as i64, + name: format!("frame #{index}"), + source: Some(Source { + path: self.debug_artifact.file_map[&location.file] + .path + .to_str() + .map(String::from), + ..Source::default() + }), + line: line_number as i64, + column: column_number as i64, + instruction_pointer_reference: ip_reference, + ..StackFrame::default() + } + }) + .collect(), + }; + let total_frames = Some(frames.len() as i64); + self.server.respond(req.success(ResponseBody::StackTrace( + StackTraceResponse { stack_frames: frames, total_frames }, + )))?; + } + Command::Disassemble(ref args) => { + let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); + let instruction_offset = args.instruction_offset.unwrap_or(0); + let (mut opcode_location, mut invalid_count) = + self.context.offset_opcode_location(&starting_ip, instruction_offset); + let mut count = args.instruction_count; + + let mut instructions: Vec = vec![]; + + // leading invalid locations (when the request goes back + // beyond the start of the program) + if invalid_count < 0 { + while invalid_count < 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count += 1; + count -= 1; + } + if count > 0 { + opcode_location = Some(OpcodeLocation::Acir(0)); + } + } + // the actual opcodes + while count > 0 && !matches!(opcode_location, None) { + instructions.push(DisassembledInstruction { + address: format!("{}", opcode_location.unwrap()), + instruction: self.context.render_opcode_at_location(&opcode_location), + ..DisassembledInstruction::default() + }); + (opcode_location, _) = + self.context.offset_opcode_location(&opcode_location, 1); + count -= 1; + } + // any remaining instruction count is beyond the valid opcode + // vector so return invalid placeholders + while count > 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count -= 1; + count -= 1; + } + + self.server.respond(req.success(ResponseBody::Disassemble( + DisassembleResponse { instructions }, + )))?; + } + Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { + self.handle_next(req)?; + } + Command::Continue(_) => { + self.handle_continue(req)?; + } + Command::Scopes(_) => { + // FIXME + self.server.respond( + req.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![] })), + )?; + } + _ => { + eprintln!("ERROR: unhandled command: {:?}", req.command); + } + } + } + Ok(()) + } + + fn handle_next(&mut self, req: Request) -> Result<(), ServerError> { + let result = self.context.next(); + eprintln!("INFO: stepped with result {result:?}"); + self.handle_execution_result(req, result) + } + + fn handle_continue(&mut self, req: Request) -> Result<(), ServerError> { + let result = self.context.cont(); + eprintln!("INFO: continue with result {result:?}"); + self.handle_execution_result(req, result) + } + + fn handle_execution_result( + &mut self, + req: Request, + result: DebugCommandResult, + ) -> Result<(), ServerError> { + match result { + DebugCommandResult::Done => { + self.server.respond(req.success(ResponseBody::Terminate))?; + self.running = false; + } + _ => { + self.server.respond(req.ack()?)?; + self.send_stopped_event(StoppedEventReason::Pause)?; + } + } + Ok(()) + } +} + +pub fn run_session( + server: Server, + solver: &B, + program: CompiledProgram, + initial_witness: WitnessMap, +) -> Result<(), ServerError> { + let debug_artifact = DebugArtifact { + debug_symbols: vec![program.debug.clone()], + file_map: program.file_map.clone(), + warnings: program.warnings.clone(), + }; + let mut session = + DapSession::new(server, solver, &program.circuit, &debug_artifact, initial_witness); + + session.run_loop() +} diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 97894d3cc1e..7e0c1605e0a 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -1,28 +1,16 @@ mod context; +mod dap; mod repl; use std::io::{Read, Write}; -use std::str::FromStr; -use acvm::acir::circuit::OpcodeLocation; +use ::dap::errors::ServerError; +use ::dap::server::Server; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; -use context::DebugContext; -use dap::errors::ServerError; -use dap::events::StoppedEventBody; -use dap::prelude::Event; -use dap::requests::Command; -use dap::responses::{ - DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, - SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, - ThreadsResponse, -}; -use dap::server::Server; -use dap::types::{DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread}; use nargo::artifacts::debug::DebugArtifact; -use nargo::ops::DefaultForeignCallExecutor; use nargo::NargoError; use noirc_driver::CompiledProgram; @@ -35,211 +23,11 @@ pub fn debug_circuit( repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness) } -fn send_stopped_event( - server: &mut Server, - reason: StoppedEventReason, -) -> Result<(), ServerError> { - let description = format!("{:?}", &reason); - server.send_event(Event::Stopped(StoppedEventBody { - reason, - description: Some(description), - thread_id: Some(0), - preserve_focus_hint: Some(false), - text: None, - all_threads_stopped: Some(false), - hit_breakpoint_ids: None, - }))?; - Ok(()) -} - pub fn run_dap_loop( - mut server: Server, + server: Server, solver: &B, program: CompiledProgram, initial_witness: WitnessMap, ) -> Result<(), ServerError> { - let debug_artifact = DebugArtifact { - debug_symbols: vec![program.debug.clone()], - file_map: program.file_map.clone(), - warnings: program.warnings.clone(), - }; - let mut context = DebugContext::new( - solver, - &program.circuit, - &debug_artifact, - initial_witness.clone(), - Box::new(DefaultForeignCallExecutor::new(true)), - ); - - if matches!(context.get_current_source_location(), None) { - // FIXME: remove this? - _ = context.next(); - } - - server.send_event(Event::Initialized)?; - send_stopped_event(&mut server, StoppedEventReason::Entry)?; - - loop { - let req = match server.poll_request()? { - Some(req) => req, - None => break, - }; - match req.command { - Command::Disconnect(_) => { - eprintln!("INFO: ending debugging session"); - server.respond(req.ack()?)?; - break; - } - Command::SetBreakpoints(ref args) => { - eprintln!("INFO: Received SetBreakpoints {:?}", args); - // FIXME: set and return the breakpoints actually set - server.respond(req.success(ResponseBody::SetBreakpoints( - SetBreakpointsResponse { breakpoints: vec![] }, - )))?; - } - Command::SetExceptionBreakpoints(_) => { - server.respond(req.success(ResponseBody::SetExceptionBreakpoints( - SetExceptionBreakpointsResponse { breakpoints: None }, - )))?; - } - Command::SetInstructionBreakpoints(ref args) => { - eprintln!("INFO: Received SetInstructionBreakpoints {:?}", args); - // FIXME: set and return the breakpoints actually set - server.respond(req.success(ResponseBody::SetInstructionBreakpoints( - SetInstructionBreakpointsResponse { breakpoints: vec![] }, - )))?; - } - Command::Threads => { - server.respond(req.success(ResponseBody::Threads(ThreadsResponse { - threads: vec![Thread { id: 0, name: "main".to_string() }], - })))?; - } - Command::StackTrace(_) => { - let opcode_location = context.get_current_opcode_location(); - let source_location = context.get_current_source_location(); - let frames = match source_location { - None => vec![], - Some(locations) => locations - .iter() - .enumerate() - .map(|(index, location)| { - let line_number = - debug_artifact.location_line_number(*location).unwrap(); - let column_number = - debug_artifact.location_column_number(*location).unwrap(); - let ip_reference = opcode_location.map(|location| location.to_string()); - StackFrame { - id: index as i64, - name: format!("frame #{index}"), - source: Some(Source { - path: debug_artifact.file_map[&location.file] - .path - .to_str() - .map(String::from), - ..Source::default() - }), - line: line_number as i64, - column: column_number as i64, - instruction_pointer_reference: ip_reference, - ..StackFrame::default() - } - }) - .collect(), - }; - let total_frames = Some(frames.len() as i64); - server.respond(req.success(ResponseBody::StackTrace(StackTraceResponse { - stack_frames: frames, - total_frames, - })))?; - } - Command::Disassemble(ref args) => { - let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); - let instruction_offset = args.instruction_offset.unwrap_or(0); - let (mut opcode_location, mut invalid_count) = - context.offset_opcode_location(&starting_ip, instruction_offset); - let mut count = args.instruction_count; - - let mut instructions: Vec = vec![]; - - // leading invalid locations (when the request goes back beyond the start of the program) - if invalid_count < 0 { - while invalid_count < 0 { - instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), - ..DisassembledInstruction::default() - }); - invalid_count += 1; - count -= 1; - } - if count > 0 { - opcode_location = Some(OpcodeLocation::Acir(0)); - } - } - // the actual opcodes - while count > 0 && !matches!(opcode_location, None) { - instructions.push(DisassembledInstruction { - address: format!("{}", opcode_location.unwrap()), - instruction: context.render_opcode_at_location(&opcode_location), - ..DisassembledInstruction::default() - }); - (opcode_location, _) = context.offset_opcode_location(&opcode_location, 1); - count -= 1; - } - // any remaining instruction count is beyond the valid opcode vector so return invalid placeholders - while count > 0 { - instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), - ..DisassembledInstruction::default() - }); - invalid_count -= 1; - count -= 1; - } - - server.respond( - req.success(ResponseBody::Disassemble(DisassembleResponse { instructions })), - )?; - } - Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { - let result = context.next(); - eprintln!("INFO: stepped with result {result:?}"); - match result { - context::DebugCommandResult::Done => { - server.respond(req.success(ResponseBody::Terminate))?; - break; - } - _ => { - server.respond(req.ack()?)?; - send_stopped_event(&mut server, StoppedEventReason::Step)? - } - } - } - Command::Continue(_) => { - let result = context.cont(); - eprintln!("INFO: continue with result {result:?}"); - match result { - context::DebugCommandResult::Done => { - server.respond(req.success(ResponseBody::Terminate))?; - break; - } - _ => { - server.respond(req.ack()?)?; - send_stopped_event(&mut server, StoppedEventReason::Pause)? - } - } - } - Command::Scopes(_) => { - // FIXME - server.respond( - req.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![] })), - )?; - } - _ => { - eprintln!("{:?}", req.command); - eprintln!("ERROR: unhandled command"); - } - } - } - Ok(()) + dap::run_session(server, solver, program, initial_witness) } From 2fc41cb4d759661892900920433bd7427bb3e4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 20 Nov 2023 10:52:16 -0500 Subject: [PATCH 09/19] chore: extract stack trace and disassemble handlers --- tooling/debugger/src/dap.rs | 185 +++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 87 deletions(-) diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index 5271064bfc9..a24445cf95e 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -110,94 +110,10 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { })))?; } Command::StackTrace(_) => { - let opcode_location = self.context.get_current_opcode_location(); - let source_location = self.context.get_current_source_location(); - let frames = match source_location { - None => vec![], - Some(locations) => locations - .iter() - .enumerate() - .map(|(index, location)| { - let line_number = - self.debug_artifact.location_line_number(*location).unwrap(); - let column_number = - self.debug_artifact.location_column_number(*location).unwrap(); - let ip_reference = - opcode_location.map(|location| location.to_string()); - StackFrame { - id: index as i64, - name: format!("frame #{index}"), - source: Some(Source { - path: self.debug_artifact.file_map[&location.file] - .path - .to_str() - .map(String::from), - ..Source::default() - }), - line: line_number as i64, - column: column_number as i64, - instruction_pointer_reference: ip_reference, - ..StackFrame::default() - } - }) - .collect(), - }; - let total_frames = Some(frames.len() as i64); - self.server.respond(req.success(ResponseBody::StackTrace( - StackTraceResponse { stack_frames: frames, total_frames }, - )))?; + self.handle_stack_trace(req)?; } - Command::Disassemble(ref args) => { - let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); - let instruction_offset = args.instruction_offset.unwrap_or(0); - let (mut opcode_location, mut invalid_count) = - self.context.offset_opcode_location(&starting_ip, instruction_offset); - let mut count = args.instruction_count; - - let mut instructions: Vec = vec![]; - - // leading invalid locations (when the request goes back - // beyond the start of the program) - if invalid_count < 0 { - while invalid_count < 0 { - instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), - ..DisassembledInstruction::default() - }); - invalid_count += 1; - count -= 1; - } - if count > 0 { - opcode_location = Some(OpcodeLocation::Acir(0)); - } - } - // the actual opcodes - while count > 0 && !matches!(opcode_location, None) { - instructions.push(DisassembledInstruction { - address: format!("{}", opcode_location.unwrap()), - instruction: self.context.render_opcode_at_location(&opcode_location), - ..DisassembledInstruction::default() - }); - (opcode_location, _) = - self.context.offset_opcode_location(&opcode_location, 1); - count -= 1; - } - // any remaining instruction count is beyond the valid opcode - // vector so return invalid placeholders - while count > 0 { - instructions.push(DisassembledInstruction { - address: String::from("---"), - instruction: String::from("---"), - ..DisassembledInstruction::default() - }); - invalid_count -= 1; - count -= 1; - } - - self.server.respond(req.success(ResponseBody::Disassemble( - DisassembleResponse { instructions }, - )))?; + Command::Disassemble(_) => { + self.handle_disassemble(req)?; } Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { self.handle_next(req)?; @@ -219,6 +135,101 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Ok(()) } + fn handle_stack_trace(&mut self, req: Request) -> Result<(), ServerError> { + let opcode_location = self.context.get_current_opcode_location(); + let source_location = self.context.get_current_source_location(); + let frames = match source_location { + None => vec![], + Some(locations) => locations + .iter() + .enumerate() + .map(|(index, location)| { + let line_number = self.debug_artifact.location_line_number(*location).unwrap(); + let column_number = + self.debug_artifact.location_column_number(*location).unwrap(); + let ip_reference = opcode_location.map(|location| location.to_string()); + StackFrame { + id: index as i64, + name: format!("frame #{index}"), + source: Some(Source { + path: self.debug_artifact.file_map[&location.file] + .path + .to_str() + .map(String::from), + ..Source::default() + }), + line: line_number as i64, + column: column_number as i64, + instruction_pointer_reference: ip_reference, + ..StackFrame::default() + } + }) + .collect(), + }; + let total_frames = Some(frames.len() as i64); + self.server.respond(req.success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames: frames, + total_frames, + })))?; + Ok(()) + } + + fn handle_disassemble(&mut self, req: Request) -> Result<(), ServerError> { + let Command::Disassemble(ref args) = req.command else { + unreachable!("handle_disassemble called on a non disassemble request"); + }; + let starting_ip = OpcodeLocation::from_str(args.memory_reference.as_str()).ok(); + let instruction_offset = args.instruction_offset.unwrap_or(0); + let (mut opcode_location, mut invalid_count) = + self.context.offset_opcode_location(&starting_ip, instruction_offset); + let mut count = args.instruction_count; + + let mut instructions: Vec = vec![]; + + // leading invalid locations (when the request goes back + // beyond the start of the program) + if invalid_count < 0 { + while invalid_count < 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count += 1; + count -= 1; + } + if count > 0 { + opcode_location = Some(OpcodeLocation::Acir(0)); + } + } + // the actual opcodes + while count > 0 && !matches!(opcode_location, None) { + instructions.push(DisassembledInstruction { + address: format!("{}", opcode_location.unwrap()), + instruction: self.context.render_opcode_at_location(&opcode_location), + ..DisassembledInstruction::default() + }); + (opcode_location, _) = self.context.offset_opcode_location(&opcode_location, 1); + count -= 1; + } + // any remaining instruction count is beyond the valid opcode + // vector so return invalid placeholders + while count > 0 { + instructions.push(DisassembledInstruction { + address: String::from("---"), + instruction: String::from("---"), + ..DisassembledInstruction::default() + }); + invalid_count -= 1; + count -= 1; + } + + self.server.respond( + req.success(ResponseBody::Disassemble(DisassembleResponse { instructions })), + )?; + Ok(()) + } + fn handle_next(&mut self, req: Request) -> Result<(), ServerError> { let result = self.context.next(); eprintln!("INFO: stepped with result {result:?}"); From b0be45bbb744b8b5ebee598e72d7376ce7e9c0c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 20 Nov 2023 11:46:37 -0500 Subject: [PATCH 10/19] chore: revert backend-interface crate rename --- Cargo.lock | 5 ++--- Cargo.toml | 1 - tooling/backend_interface/Cargo.toml | 2 +- tooling/debugger/Cargo.toml | 1 - tooling/nargo_cli/Cargo.toml | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eff9f4146c7..48c3bef7a42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -422,7 +422,7 @@ dependencies = [ ] [[package]] -name = "backend_interface" +name = "backend-interface" version = "0.11.0" dependencies = [ "acvm", @@ -2425,7 +2425,7 @@ dependencies = [ "assert_cmd", "assert_fs", "async-lsp", - "backend_interface", + "backend-interface", "barretenberg_blackbox_solver", "bb_abstraction_leaks", "build-data", @@ -2533,7 +2533,6 @@ name = "noir_debugger" version = "0.19.4" dependencies = [ "acvm", - "backend_interface", "codespan-reporting", "dap", "easy-repl", diff --git a/Cargo.toml b/Cargo.toml index 0726061d8d9..540a0cb779c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,6 @@ noirc_printable_type = { path = "compiler/noirc_printable_type" } noir_wasm = { path = "compiler/wasm" } # Noir tooling workspace dependencies -backend_interface = { path = "tooling/backend_interface" } nargo = { path = "tooling/nargo" } nargo_fmt = { path = "tooling/nargo_fmt" } nargo_cli = { path = "tooling/nargo_cli" } diff --git a/tooling/backend_interface/Cargo.toml b/tooling/backend_interface/Cargo.toml index 324d7a5b159..14b1609dd4a 100644 --- a/tooling/backend_interface/Cargo.toml +++ b/tooling/backend_interface/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "backend_interface" +name = "backend-interface" description = "The definition of the backend CLI interface which Nargo uses for proving/verifying ACIR circuits." version = "0.11.0" authors.workspace = true diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index e591877168f..e8ebfb4f2cc 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true [dependencies] acvm.workspace = true -backend_interface.workspace = true nargo.workspace = true nargo_toml.workspace = true noirc_printable_type.workspace = true diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index 93df78b16a1..8f2e0cca40c 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -50,7 +50,7 @@ tokio = { version = "1.0", features = ["io-std"] } dap.workspace = true # Backends -backend_interface.workspace = true +backend-interface = { path = "../backend_interface" } bb_abstraction_leaks.workspace = true [target.'cfg(not(unix))'.dependencies] From 9b1d25161e0421b706fe33767554d7df90546fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 20 Nov 2023 16:24:26 -0500 Subject: [PATCH 11/19] chore: remove unneeded dependency --- Cargo.lock | 1 - tooling/debugger/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48c3bef7a42..8c7fb83ced3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2537,7 +2537,6 @@ dependencies = [ "dap", "easy-repl", "nargo", - "nargo_toml", "noirc_driver", "noirc_errors", "noirc_printable_type", diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index e8ebfb4f2cc..68e37711d2a 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] acvm.workspace = true nargo.workspace = true -nargo_toml.workspace = true noirc_printable_type.workspace = true noirc_errors.workspace = true noirc_driver.workspace = true From a6503684dd2da7e7acd6140f1276ab2312667381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Thu, 23 Nov 2023 21:35:42 -0500 Subject: [PATCH 12/19] feat: implement instruction and source breakpoints --- Cargo.lock | 1 + tooling/debugger/Cargo.toml | 1 + tooling/debugger/src/dap.rs | 196 +++++++++++++++++++++++---- tooling/nargo_cli/src/cli/dap_cmd.rs | 14 +- 4 files changed, 183 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c7fb83ced3..e11acc5af44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2536,6 +2536,7 @@ dependencies = [ "codespan-reporting", "dap", "easy-repl", + "fm", "nargo", "noirc_driver", "noirc_errors", diff --git a/tooling/debugger/Cargo.toml b/tooling/debugger/Cargo.toml index 68e37711d2a..fba4d028d05 100644 --- a/tooling/debugger/Cargo.toml +++ b/tooling/debugger/Cargo.toml @@ -14,6 +14,7 @@ nargo.workspace = true noirc_printable_type.workspace = true noirc_errors.workspace = true noirc_driver.workspace = true +fm.workspace = true thiserror.workspace = true codespan-reporting.workspace = true dap.workspace = true diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index a24445cf95e..e485517776c 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -1,9 +1,11 @@ +use std::collections::BTreeMap; use std::io::{Read, Write}; use std::str::FromStr; use acvm::acir::circuit::{Circuit, OpcodeLocation}; use acvm::acir::native_types::WitnessMap; use acvm::BlackBoxFunctionSolver; +use codespan_reporting::files::{Files, SimpleFile}; use crate::context::DebugCommandResult; use crate::context::DebugContext; @@ -13,15 +15,18 @@ use dap::events::StoppedEventBody; use dap::prelude::Event; use dap::requests::{Command, Request}; use dap::responses::{ - DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, + ContinueResponse, DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, ThreadsResponse, }; use dap::server::Server; -use dap::types::{DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread}; +use dap::types::{ + Breakpoint, DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread, +}; use nargo::artifacts::debug::DebugArtifact; use nargo::ops::DefaultForeignCallExecutor; +use fm::FileId; use noirc_driver::CompiledProgram; pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> { @@ -29,8 +34,11 @@ pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> { context: DebugContext<'a, B>, debug_artifact: &'a DebugArtifact, running: bool, + source_to_opcodes: BTreeMap>, } +// BTreeMap + impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { pub fn new( server: Server, @@ -39,6 +47,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, ) -> Self { + let source_to_opcodes = Self::build_source_to_opcode_debug_mappings(debug_artifact); let context = DebugContext::new( solver, circuit, @@ -46,7 +55,47 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { initial_witness, Box::new(DefaultForeignCallExecutor::new(true)), ); - Self { server, context, debug_artifact, running: false } + Self { server, context, debug_artifact, source_to_opcodes, running: false } + } + + /// Builds a map from FileId to an ordered vector of tuples with line + /// numbers and opcode locations correspoding to those line numbers + fn build_source_to_opcode_debug_mappings( + debug_artifact: &'a DebugArtifact, + ) -> BTreeMap> { + let mut result = BTreeMap::new(); + if debug_artifact.debug_symbols.is_empty() { + return result; + } + let locations = &debug_artifact.debug_symbols[0].locations; + let mut simple_files = BTreeMap::new(); + debug_artifact.file_map.iter().for_each(|(file_id, debug_file)| { + simple_files.insert( + file_id, + SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()), + ); + }); + + locations.iter().for_each(|(opcode_location, source_locations)| { + if source_locations.is_empty() { + return; + } + let source_location = source_locations[0]; + let span = source_location.span; + let file_id = source_location.file; + let Ok(line_index) = &simple_files[&file_id].line_index((), span.start() as usize) else { + return; + }; + let line_number = line_index + 1; + + if result.contains_key(&file_id) { + result.get_mut(&file_id).unwrap().push((line_number, *opcode_location)); + } else { + result.insert(file_id, vec![(line_number, *opcode_location)]); + } + }); + result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| x.0)); + result } fn send_stopped_event(&mut self, reason: StoppedEventReason) -> Result<(), ServerError> { @@ -85,24 +134,16 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.server.respond(req.ack()?)?; break; } - Command::SetBreakpoints(ref args) => { - eprintln!("INFO: Received SetBreakpoints {:?}", args); - // FIXME: set and return the breakpoints actually set - self.server.respond(req.success(ResponseBody::SetBreakpoints( - SetBreakpointsResponse { breakpoints: vec![] }, - )))?; + Command::SetBreakpoints(_) => { + self.handle_set_source_breakpoints(req)?; } Command::SetExceptionBreakpoints(_) => { self.server.respond(req.success(ResponseBody::SetExceptionBreakpoints( SetExceptionBreakpointsResponse { breakpoints: None }, )))?; } - Command::SetInstructionBreakpoints(ref args) => { - eprintln!("INFO: Received SetInstructionBreakpoints {:?}", args); - // FIXME: set and return the breakpoints actually set - self.server.respond(req.success(ResponseBody::SetInstructionBreakpoints( - SetInstructionBreakpointsResponse { breakpoints: vec![] }, - )))?; + Command::SetInstructionBreakpoints(_) => { + self.handle_set_instruction_breakpoints(req)?; } Command::Threads => { self.server.respond(req.success(ResponseBody::Threads(ThreadsResponse { @@ -233,32 +274,139 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { fn handle_next(&mut self, req: Request) -> Result<(), ServerError> { let result = self.context.next(); eprintln!("INFO: stepped with result {result:?}"); - self.handle_execution_result(req, result) + self.server.respond(req.ack()?)?; + self.handle_execution_result(result) } fn handle_continue(&mut self, req: Request) -> Result<(), ServerError> { let result = self.context.cont(); eprintln!("INFO: continue with result {result:?}"); - self.handle_execution_result(req, result) + self.server.respond(req.success(ResponseBody::Continue(ContinueResponse { + all_threads_continued: Some(true), + })))?; + self.handle_execution_result(result) } - fn handle_execution_result( - &mut self, - req: Request, - result: DebugCommandResult, - ) -> Result<(), ServerError> { + fn handle_execution_result(&mut self, result: DebugCommandResult) -> Result<(), ServerError> { match result { DebugCommandResult::Done => { - self.server.respond(req.success(ResponseBody::Terminate))?; self.running = false; } _ => { - self.server.respond(req.ack()?)?; self.send_stopped_event(StoppedEventReason::Pause)?; } } Ok(()) } + + fn handle_set_instruction_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + let Command::SetInstructionBreakpoints(ref args) = req.command else { + unreachable!("handle_set_instruction_breakpoints called on a different request"); + }; + // FIXME: clear previous instruction breakpoints + let breakpoints = args.breakpoints.iter().filter_map(|breakpoint| { + let Ok(location) = OpcodeLocation::from_str(breakpoint.instruction_reference.as_str()) else { + return None; + }; + if !self.context.is_valid_opcode_location(&location) { + return None; + } + if self.context.add_breakpoint(location) { + Some( + Breakpoint { + verified: true, + instruction_reference: Some(breakpoint.instruction_reference.clone()), + ..Breakpoint::default() + } + ) + } else { + None + } + }).collect(); + self.server.respond(req.success(ResponseBody::SetInstructionBreakpoints( + SetInstructionBreakpointsResponse { breakpoints }, + )))?; + Ok(()) + } + + fn find_file_id(&self, source_path: &str) -> Option { + let file_map = &self.debug_artifact.file_map; + let found = file_map.iter().find(|(_, debug_file)| match debug_file.path.to_str() { + Some(debug_file_path) => debug_file_path == source_path, + None => false, + }); + if let Some(iter) = found { + Some(*iter.0) + } else { + None + } + } + + fn find_opcode_for_source_location(&self, source: &str, line: i64) -> Option { + let line = line as usize; + let Some(file_id) = self.find_file_id(source) else { + return None; + }; + if self.debug_artifact.debug_symbols.is_empty() { + return None; + } + let Some(line_to_opcodes) = self.source_to_opcodes.get(&file_id) else { + return None; + }; + let found_index = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { + Ok(index) => line_to_opcodes[index].1, + Err(index) => line_to_opcodes[index].1, + }; + Some(found_index) + } + + fn handle_set_source_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + let Command::SetBreakpoints(ref args) = req.command else { + unreachable!("handle_set_source_breakpoints called on a different request"); + }; + let Some(ref source) = &args.source.path else { + self.server.respond( + req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints: vec![] })), + )?; + return Ok(()); + }; + let Some(ref breakpoints) = &args.breakpoints else { + self.server.respond( + req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints: vec![] })), + )?; + return Ok(()); + }; + // FIXME: clear previous source breakpoints on this source + let breakpoints = breakpoints + .iter() + .filter_map(|breakpoint| { + let line = breakpoint.line; + let Some(location) = self.find_opcode_for_source_location(source, line) else { + return None; + }; + if !self.context.is_valid_opcode_location(&location) { + return None; + } + if self.context.add_breakpoint(location) { + let instruction_reference = format!("{}", location); + Some(Breakpoint { + verified: true, + source: Some(args.source.clone()), + instruction_reference: Some(instruction_reference), + line: Some(line), + ..Breakpoint::default() + }) + } else { + None + } + }) + .collect(); + + self.server.respond( + req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })), + )?; + Ok(()) + } } pub fn run_session( diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 26a6f3cc4da..db344b4bd37 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -31,18 +31,22 @@ struct LoadError(&'static str); fn find_workspace(project_folder: &str, package: Option<&str>) -> Option { let Ok(toml_path) = get_package_manifest(Path::new(project_folder)) else { + eprintln!("ERROR: Failed to get package manifest"); return None; }; let package = package.and_then(|p| serde_json::from_str::(p).ok()); let selection = package.map_or(PackageSelection::DefaultOrAll, PackageSelection::Selected); - let Ok(workspace) = resolve_workspace_from_toml( + match resolve_workspace_from_toml( &toml_path, selection, Some(NOIR_ARTIFACT_VERSION_STRING.to_string()), - ) else { - return None; - }; - Some(workspace) + ) { + Ok(workspace) => Some(workspace), + Err(err) => { + eprintln!("ERROR: Failed to resolve workspace: {}", err.to_string()); + None + } + } } fn load_and_compile_project( From 27cb858b303609e41aa39e7ceff4eada4743495e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 27 Nov 2023 17:35:19 -0500 Subject: [PATCH 13/19] feat: handle breakpoints correctly and emit events when they are reached --- tooling/debugger/src/context.rs | 4 + tooling/debugger/src/dap.rs | 221 ++++++++++++++++++++++++-------- 2 files changed, 171 insertions(+), 54 deletions(-) diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index a25bb203752..62778088b4f 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -414,6 +414,10 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.breakpoints.iter() } + pub(super) fn clear_breakpoints(&mut self) { + self.breakpoints.clear(); + } + pub(super) fn is_solved(&self) -> bool { matches!(self.acvm.get_status(), ACVMStatus::Solved) } diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index e485517776c..12d61661289 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -13,7 +13,7 @@ use crate::context::DebugContext; use dap::errors::ServerError; use dap::events::StoppedEventBody; use dap::prelude::Event; -use dap::requests::{Command, Request}; +use dap::requests::{Command, Request, SetBreakpointsArguments}; use dap::responses::{ ContinueResponse, DisassembleResponse, ResponseBody, ScopesResponse, SetBreakpointsResponse, SetExceptionBreakpointsResponse, SetInstructionBreakpointsResponse, StackTraceResponse, @@ -35,6 +35,9 @@ pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> { debug_artifact: &'a DebugArtifact, running: bool, source_to_opcodes: BTreeMap>, + next_breakpoint_id: i64, + instruction_breakpoints: Vec<(OpcodeLocation, i64)>, + source_breakpoints: BTreeMap>, } // BTreeMap @@ -55,7 +58,16 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { initial_witness, Box::new(DefaultForeignCallExecutor::new(true)), ); - Self { server, context, debug_artifact, source_to_opcodes, running: false } + Self { + server, + context, + debug_artifact, + source_to_opcodes, + running: false, + next_breakpoint_id: 1, + instruction_breakpoints: vec![], + source_breakpoints: BTreeMap::new(), + } } /// Builds a map from FileId to an ordered vector of tuples with line @@ -287,42 +299,120 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.handle_execution_result(result) } + fn find_breakpoints_at_location(&self, opcode_location: &OpcodeLocation) -> Vec { + let mut result = vec![]; + for (location, id) in &self.instruction_breakpoints { + if opcode_location == location { + result.push(*id); + } + } + for (_, breakpoints) in &self.source_breakpoints { + for (location, id) in breakpoints { + if opcode_location == location { + result.push(*id); + } + } + } + result + } + fn handle_execution_result(&mut self, result: DebugCommandResult) -> Result<(), ServerError> { match result { DebugCommandResult::Done => { self.running = false; } - _ => { - self.send_stopped_event(StoppedEventReason::Pause)?; + DebugCommandResult::Ok => { + self.server.send_event(Event::Stopped(StoppedEventBody { + reason: StoppedEventReason::Pause, + description: None, + thread_id: Some(0), + preserve_focus_hint: Some(false), + text: None, + all_threads_stopped: Some(false), + hit_breakpoint_ids: None, + }))?; + } + DebugCommandResult::BreakpointReached(location) => { + let breakpoint_ids = self.find_breakpoints_at_location(&location); + self.server.send_event(Event::Stopped(StoppedEventBody { + reason: StoppedEventReason::Breakpoint, + description: Some(String::from("Paused at breakpoint")), + thread_id: Some(0), + preserve_focus_hint: Some(false), + text: None, + all_threads_stopped: Some(false), + hit_breakpoint_ids: Some(breakpoint_ids), + }))?; + } + DebugCommandResult::Error(err) => { + self.server.send_event(Event::Stopped(StoppedEventBody { + reason: StoppedEventReason::Exception, + description: Some(format!("{err:?}")), + thread_id: Some(0), + preserve_focus_hint: Some(false), + text: None, + all_threads_stopped: Some(false), + hit_breakpoint_ids: None, + }))?; } } Ok(()) } + fn get_next_breakpoint_id(&mut self) -> i64 { + let id = self.next_breakpoint_id; + self.next_breakpoint_id += 1; + id + } + + fn reinstall_breakpoints(&mut self) { + self.context.clear_breakpoints(); + for (location, _) in &self.instruction_breakpoints { + self.context.add_breakpoint(*location); + } + for (_, breakpoints) in &self.source_breakpoints { + for (location, _) in breakpoints { + self.context.add_breakpoint(*location); + } + } + } + fn handle_set_instruction_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { let Command::SetInstructionBreakpoints(ref args) = req.command else { unreachable!("handle_set_instruction_breakpoints called on a different request"); }; - // FIXME: clear previous instruction breakpoints - let breakpoints = args.breakpoints.iter().filter_map(|breakpoint| { + + // compute breakpoints to set and return + let mut breakpoints_to_set: Vec<(OpcodeLocation, i64)> = vec![]; + let breakpoints: Vec = args.breakpoints.iter().map(|breakpoint| { let Ok(location) = OpcodeLocation::from_str(breakpoint.instruction_reference.as_str()) else { - return None; + return Breakpoint { + verified: false, + message: Some(String::from("Missing instruction reference")), + ..Breakpoint::default() + }; }; if !self.context.is_valid_opcode_location(&location) { - return None; + return Breakpoint { + verified: false, + message: Some(String::from("Invalid opcode location")), + ..Breakpoint::default() + }; } - if self.context.add_breakpoint(location) { - Some( - Breakpoint { - verified: true, - instruction_reference: Some(breakpoint.instruction_reference.clone()), - ..Breakpoint::default() - } - ) - } else { - None + let id = self.get_next_breakpoint_id(); + breakpoints_to_set.push((location, id)); + Breakpoint { + id: Some(id), + verified: true, + ..Breakpoint::default() } }).collect(); + + // actually set the computed breakpoints + self.instruction_breakpoints = breakpoints_to_set; + self.reinstall_breakpoints(); + + // response to request self.server.respond(req.success(ResponseBody::SetInstructionBreakpoints( SetInstructionBreakpointsResponse { breakpoints }, )))?; @@ -342,15 +432,22 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { } } - fn find_opcode_for_source_location(&self, source: &str, line: i64) -> Option { + // FIXME: there are four possibilities for the return value of this function: + // 1. the source location is not found -> None + // 2. an exact unique location is found -> Some(opcode_location) + // 3. an exact but not unique location is found (ie. a source location may + // be mapped to multiple opcodes, and those may be disjoint, for example for + // functions called multiple times throughout the program) + // 4. exact location is not found, so an opcode for a nearby source location + // is returned (this again could actually be more than one opcodes) + // Case 3 is not supported yet, and 4 is not correctly handled. + fn find_opcode_for_source_location( + &self, + file_id: &FileId, + line: i64, + ) -> Option { let line = line as usize; - let Some(file_id) = self.find_file_id(source) else { - return None; - }; - if self.debug_artifact.debug_symbols.is_empty() { - return None; - } - let Some(line_to_opcodes) = self.source_to_opcodes.get(&file_id) else { + let Some(line_to_opcodes) = self.source_to_opcodes.get(file_id) else { return None; }; let found_index = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { @@ -360,48 +457,64 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Some(found_index) } - fn handle_set_source_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { - let Command::SetBreakpoints(ref args) = req.command else { - unreachable!("handle_set_source_breakpoints called on a different request"); - }; + fn map_source_breakpoints(&mut self, args: &SetBreakpointsArguments) -> Vec { let Some(ref source) = &args.source.path else { - self.server.respond( - req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints: vec![] })), - )?; - return Ok(()); + return vec![]; + }; + let Some(file_id) = self.find_file_id(source) else { + eprintln!("WARN: file ID for source {source} not found"); + return vec![]; }; let Some(ref breakpoints) = &args.breakpoints else { - self.server.respond( - req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints: vec![] })), - )?; - return Ok(()); + return vec![]; }; - // FIXME: clear previous source breakpoints on this source + let mut breakpoints_to_set: Vec<(OpcodeLocation, i64)> = vec![]; let breakpoints = breakpoints .iter() - .filter_map(|breakpoint| { + .map(|breakpoint| { let line = breakpoint.line; - let Some(location) = self.find_opcode_for_source_location(source, line) else { - return None; + let Some(location) = self.find_opcode_for_source_location(&file_id, line) else { + return Breakpoint { + verified: false, + message: Some(String::from("Source location cannot be matched to opcode location")), + ..Breakpoint::default() + }; }; + // FIXME: line will not necessarily be the one requested; we + // should do the reverse mapping and retrieve the actual source + // code line number if !self.context.is_valid_opcode_location(&location) { - return None; - } - if self.context.add_breakpoint(location) { - let instruction_reference = format!("{}", location); - Some(Breakpoint { - verified: true, - source: Some(args.source.clone()), - instruction_reference: Some(instruction_reference), - line: Some(line), + return Breakpoint { + verified: false, + message: Some(String::from("Invalid opcode location")), ..Breakpoint::default() - }) - } else { - None + }; + } + let instruction_reference = format!("{}", location); + let breakpoint_id = self.get_next_breakpoint_id(); + breakpoints_to_set.push((location, breakpoint_id)); + Breakpoint { + id: Some(breakpoint_id), + verified: true, + source: Some(args.source.clone()), + instruction_reference: Some(instruction_reference), + line: Some(line), + ..Breakpoint::default() } }) .collect(); + self.source_breakpoints.insert(file_id, breakpoints_to_set); + + breakpoints + } + + fn handle_set_source_breakpoints(&mut self, req: Request) -> Result<(), ServerError> { + let Command::SetBreakpoints(ref args) = req.command else { + unreachable!("handle_set_source_breakpoints called on a different request"); + }; + let breakpoints = self.map_source_breakpoints(args); + self.reinstall_breakpoints(); self.server.respond( req.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })), )?; From 588c681167bdb7abb89fb0151e3a36e1f879303e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 27 Nov 2023 18:12:23 -0500 Subject: [PATCH 14/19] feat: handle stepping granularity --- tooling/debugger/src/dap.rs | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index 12d61661289..9f249923dfa 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -21,7 +21,7 @@ use dap::responses::{ }; use dap::server::Server; use dap::types::{ - Breakpoint, DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread, + Breakpoint, DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread, SteppingGranularity, }; use nargo::artifacts::debug::DebugArtifact; use nargo::ops::DefaultForeignCallExecutor; @@ -168,8 +168,26 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Command::Disassemble(_) => { self.handle_disassemble(req)?; } - Command::Next(_) | Command::StepIn(_) | Command::StepOut(_) => { - self.handle_next(req)?; + Command::StepIn(ref args) => { + let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } + } + Command::StepOut(ref args) => { + let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } + } + Command::Next(ref args) => { + let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + match granularity { + SteppingGranularity::Instruction => self.handle_step(req)?, + _ => self.handle_next(req)?, + } } Command::Continue(_) => { self.handle_continue(req)?; @@ -283,9 +301,16 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Ok(()) } + fn handle_step(&mut self, req: Request) -> Result<(), ServerError> { + let result = self.context.step_into_opcode(); + eprintln!("INFO: stepped by instruction with result {result:?}"); + self.server.respond(req.ack()?)?; + self.handle_execution_result(result) + } + fn handle_next(&mut self, req: Request) -> Result<(), ServerError> { let result = self.context.next(); - eprintln!("INFO: stepped with result {result:?}"); + eprintln!("INFO: stepped by statement with result {result:?}"); self.server.respond(req.ack()?)?; self.handle_execution_result(result) } From 8aa9327ce42a8d53cecfb7ae2f4e488a6bc45297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 28 Nov 2023 20:58:36 -0500 Subject: [PATCH 15/19] chore: cargo fmt & clippy --- tooling/debugger/src/dap.rs | 29 ++++++++++++++-------------- tooling/nargo_cli/src/cli/dap_cmd.rs | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index 9f249923dfa..f6e49e451f8 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -1,3 +1,4 @@ +use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::io::{Read, Write}; use std::str::FromStr; @@ -21,7 +22,8 @@ use dap::responses::{ }; use dap::server::Server; use dap::types::{ - Breakpoint, DisassembledInstruction, Source, StackFrame, StoppedEventReason, Thread, SteppingGranularity, + Breakpoint, DisassembledInstruction, Source, StackFrame, SteppingGranularity, + StoppedEventReason, Thread, }; use nargo::artifacts::debug::DebugArtifact; use nargo::ops::DefaultForeignCallExecutor; @@ -100,10 +102,10 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { }; let line_number = line_index + 1; - if result.contains_key(&file_id) { - result.get_mut(&file_id).unwrap().push((line_number, *opcode_location)); + if let Entry::Vacant(e) = result.entry(file_id) { + e.insert(vec![(line_number, *opcode_location)]); } else { - result.insert(file_id, vec![(line_number, *opcode_location)]); + result.get_mut(&file_id).unwrap().push((line_number, *opcode_location)); } }); result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| x.0)); @@ -169,21 +171,24 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.handle_disassemble(req)?; } Command::StepIn(ref args) => { - let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); match granularity { SteppingGranularity::Instruction => self.handle_step(req)?, _ => self.handle_next(req)?, } } Command::StepOut(ref args) => { - let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); match granularity { SteppingGranularity::Instruction => self.handle_step(req)?, _ => self.handle_next(req)?, } } Command::Next(ref args) => { - let granularity = args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); + let granularity = + args.granularity.as_ref().unwrap_or(&SteppingGranularity::Statement); match granularity { SteppingGranularity::Instruction => self.handle_step(req)?, _ => self.handle_next(req)?, @@ -331,7 +336,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { result.push(*id); } } - for (_, breakpoints) in &self.source_breakpoints { + for breakpoints in self.source_breakpoints.values() { for (location, id) in breakpoints { if opcode_location == location { result.push(*id); @@ -395,7 +400,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { for (location, _) in &self.instruction_breakpoints { self.context.add_breakpoint(*location); } - for (_, breakpoints) in &self.source_breakpoints { + for breakpoints in self.source_breakpoints.values() { for (location, _) in breakpoints { self.context.add_breakpoint(*location); } @@ -450,11 +455,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Some(debug_file_path) => debug_file_path == source_path, None => false, }); - if let Some(iter) = found { - Some(*iter.0) - } else { - None - } + found.map(|iter| *iter.0) } // FIXME: there are four possibilities for the return value of this function: diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index db344b4bd37..3c5b23511b0 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -43,7 +43,7 @@ fn find_workspace(project_folder: &str, package: Option<&str>) -> Option Some(workspace), Err(err) => { - eprintln!("ERROR: Failed to resolve workspace: {}", err.to_string()); + eprintln!("ERROR: Failed to resolve workspace: {err}"); None } } From 5315992507f0e84815e2163d2cd06d424efe9454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 12 Dec 2023 11:30:24 -0500 Subject: [PATCH 16/19] Apply suggestions from code review Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- tooling/debugger/src/dap.rs | 35 +++++++++++++--------------- tooling/nargo_cli/src/cli/dap_cmd.rs | 34 +++++++++++---------------- 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index f6e49e451f8..64f00787e0b 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -1,4 +1,3 @@ -use std::collections::btree_map::Entry; use std::collections::BTreeMap; use std::io::{Read, Write}; use std::str::FromStr; @@ -73,22 +72,24 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { } /// Builds a map from FileId to an ordered vector of tuples with line - /// numbers and opcode locations correspoding to those line numbers + /// numbers and opcode locations corresponding to those line numbers fn build_source_to_opcode_debug_mappings( debug_artifact: &'a DebugArtifact, ) -> BTreeMap> { - let mut result = BTreeMap::new(); if debug_artifact.debug_symbols.is_empty() { - return result; + return BTreeMap::new(); } let locations = &debug_artifact.debug_symbols[0].locations; - let mut simple_files = BTreeMap::new(); - debug_artifact.file_map.iter().for_each(|(file_id, debug_file)| { - simple_files.insert( - file_id, - SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()), - ); - }); + let simple_files: BTreeMap<_, _> = debug_artifact + .file_map + .iter() + .map(|(file_id, debug_file)| { + ( + file_id, + SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()), + ) + }) + .collect(); locations.iter().for_each(|(opcode_location, source_locations)| { if source_locations.is_empty() { @@ -102,11 +103,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { }; let line_number = line_index + 1; - if let Entry::Vacant(e) = result.entry(file_id) { - e.insert(vec![(line_number, *opcode_location)]); - } else { - result.get_mut(&file_id).unwrap().push((line_number, *opcode_location)); - } + result.entry(file_id).or_default().push((line_number, *opcode_location)); }); result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| x.0)); result @@ -555,9 +552,9 @@ pub fn run_session( initial_witness: WitnessMap, ) -> Result<(), ServerError> { let debug_artifact = DebugArtifact { - debug_symbols: vec![program.debug.clone()], - file_map: program.file_map.clone(), - warnings: program.warnings.clone(), + debug_symbols: vec![program.debug], + file_map: program.file_map, + warnings: program.warnings, }; let mut session = DapSession::new(server, solver, &program.circuit, &debug_artifact, initial_witness); diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 3c5b23511b0..8c0d30b91f4 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -55,36 +55,30 @@ fn load_and_compile_project( package: Option<&str>, prover_name: &str, ) -> Result<(CompiledProgram, WitnessMap), LoadError> { - let Some(workspace) = find_workspace(project_folder, package) else { - return Err(LoadError("Cannot open workspace")); - }; - let Ok((np_language, opcode_support)) = backend.get_backend_info() else { - return Err(LoadError("Failed to get backend info")); - }; - let Some(package) = workspace.into_iter().find(|p| p.is_binary()) else { - return Err(LoadError("No matching binary packages found in workspace")); - }; + let workspace = + find_workspace(project_folder, package).ok_or(LoadError("Cannot open workspace"))?; + let (np_language, opcode_support) = + backend.get_backend_info().map_err(|_| LoadError("Failed to get backend info"))?; + let package = workspace.into_iter().find(|p| p.is_binary()).ok_or(LoadError("No matching binary packages found in workspace"))?; - let Ok(compiled_program) = compile_bin_package( + let compiled_program = compile_bin_package( &workspace, package, &CompileOptions::default(), np_language, &opcode_support, - ) else { - return Err(LoadError("Failed to compile project")); - }; - let Ok((inputs_map, _)) = read_inputs_from_file( + ).map_err(|_| LoadError("Failed to compile project")); + + let (inputs_map, _) = read_inputs_from_file( &package.root_dir, prover_name, Format::Toml, &compiled_program.abi, - ) else { - return Err(LoadError("Failed to read program inputs")); - }; - let Ok(initial_witness) = compiled_program.abi.encode(&inputs_map, None) else { - return Err(LoadError("Failed to encode inputs")); - }; + ).map_err(|_| LoadError("Failed to read program inputs"))?; + let initial_witness = compiled_program + .abi + .encode(&inputs_map, None) + .map_err(|_| LoadError("Failed to encode inputs"))?; Ok((compiled_program, initial_witness)) } From 6a52ab24d72974a42f510bc71e19ed78ddadda7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 12 Dec 2023 11:48:13 -0500 Subject: [PATCH 17/19] chore: fix compilation errors and improve some comments --- tooling/debugger/src/dap.rs | 13 +++++++++---- tooling/nargo_cli/src/cli/dap_cmd.rs | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index 64f00787e0b..1cc05e28a6b 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -91,6 +91,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { }) .collect(); + let mut result: BTreeMap> = BTreeMap::new(); locations.iter().for_each(|(opcode_location, source_locations)| { if source_locations.is_empty() { return; @@ -127,7 +128,10 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.running = true; if matches!(self.context.get_current_source_location(), None) { - // FIXME: remove this? + // TODO: remove this? This is to ensure that the tool has a proper + // source location to show when first starting the debugger, but + // maybe the default behavior should be to start executing until the + // first breakpoint set. _ = self.context.next(); } @@ -195,7 +199,8 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.handle_continue(req)?; } Command::Scopes(_) => { - // FIXME + // FIXME: this needs a proper implementation when we can + // show the parameters and variables self.server.respond( req.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![] })), )?; @@ -455,7 +460,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { found.map(|iter| *iter.0) } - // FIXME: there are four possibilities for the return value of this function: + // TODO: there are four possibilities for the return value of this function: // 1. the source location is not found -> None // 2. an exact unique location is found -> Some(opcode_location) // 3. an exact but not unique location is found (ie. a source location may @@ -503,7 +508,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { ..Breakpoint::default() }; }; - // FIXME: line will not necessarily be the one requested; we + // TODO: line will not necessarily be the one requested; we // should do the reverse mapping and retrieve the actual source // code line number if !self.context.is_valid_opcode_location(&location) { diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 8c0d30b91f4..4c1106da668 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -59,7 +59,10 @@ fn load_and_compile_project( find_workspace(project_folder, package).ok_or(LoadError("Cannot open workspace"))?; let (np_language, opcode_support) = backend.get_backend_info().map_err(|_| LoadError("Failed to get backend info"))?; - let package = workspace.into_iter().find(|p| p.is_binary()).ok_or(LoadError("No matching binary packages found in workspace"))?; + let package = workspace + .into_iter() + .find(|p| p.is_binary()) + .ok_or(LoadError("No matching binary packages found in workspace"))?; let compiled_program = compile_bin_package( &workspace, @@ -67,14 +70,12 @@ fn load_and_compile_project( &CompileOptions::default(), np_language, &opcode_support, - ).map_err(|_| LoadError("Failed to compile project")); - - let (inputs_map, _) = read_inputs_from_file( - &package.root_dir, - prover_name, - Format::Toml, - &compiled_program.abi, - ).map_err(|_| LoadError("Failed to read program inputs"))?; + ) + .map_err(|_| LoadError("Failed to compile project"))?; + + let (inputs_map, _) = + read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &compiled_program.abi) + .map_err(|_| LoadError("Failed to read program inputs"))?; let initial_witness = compiled_program .abi .encode(&inputs_map, None) From 51238e1394342fd9d543c458ca7c5fda7fe64851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 19 Dec 2023 12:56:29 -0500 Subject: [PATCH 18/19] chore: fix compilation errors after merge --- tooling/nargo_cli/src/cli/dap_cmd.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tooling/nargo_cli/src/cli/dap_cmd.rs b/tooling/nargo_cli/src/cli/dap_cmd.rs index 4c1106da668..29e696ea608 100644 --- a/tooling/nargo_cli/src/cli/dap_cmd.rs +++ b/tooling/nargo_cli/src/cli/dap_cmd.rs @@ -2,10 +2,13 @@ use acvm::acir::native_types::WitnessMap; use backend_interface::Backend; use clap::Args; use nargo::constants::PROVER_INPUT_FILE; +use nargo::insert_all_files_for_workspace_into_file_manager; use nargo::workspace::Workspace; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; use noirc_abi::input_parser::Format; -use noirc_driver::{CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_driver::{ + file_manager_with_stdlib, CompileOptions, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, +}; use noirc_frontend::graph::CrateName; use std::io::{BufReader, BufWriter, Read, Write}; @@ -57,19 +60,23 @@ fn load_and_compile_project( ) -> Result<(CompiledProgram, WitnessMap), LoadError> { let workspace = find_workspace(project_folder, package).ok_or(LoadError("Cannot open workspace"))?; - let (np_language, opcode_support) = + + let expression_width = backend.get_backend_info().map_err(|_| LoadError("Failed to get backend info"))?; let package = workspace .into_iter() .find(|p| p.is_binary()) .ok_or(LoadError("No matching binary packages found in workspace"))?; + let mut workspace_file_manager = file_manager_with_stdlib(std::path::Path::new("")); + insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager); + let compiled_program = compile_bin_package( + &workspace_file_manager, &workspace, package, &CompileOptions::default(), - np_language, - &opcode_support, + expression_width, ) .map_err(|_| LoadError("Failed to compile project"))?; @@ -130,9 +137,7 @@ fn loop_uninitialized_dap( Ok((compiled_program, initial_witness)) => { server.respond(req.ack()?)?; - #[allow(deprecated)] - let blackbox_solver = - barretenberg_blackbox_solver::BarretenbergSolver::new(); + let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver::new(); noir_debugger::run_dap_loop( server, From cbda3059b1bb3187f72673d11607d6c76b9e7388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 19 Dec 2023 14:31:11 -0500 Subject: [PATCH 19/19] chore: fix test compilation --- tooling/debugger/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index 45846ec02e1..06855e75c97 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -663,7 +663,7 @@ mod tests { bytecode: vec![BrilligOpcode::Stop, BrilligOpcode::Stop, BrilligOpcode::Stop], predicate: None, }), - Opcode::Arithmetic(Expression::default()), + Opcode::AssertZero(Expression::default()), ]; let circuit = Circuit { opcodes, ..Circuit::default() }; let debug_artifact =