Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 18 additions & 11 deletions compiler/noirc_frontend/src/debug/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
use crate::ast::PathSegment;
use crate::parse_program;
use crate::parser::ParsedModule;
use crate::parser::{ParsedModule, ParsedSubModule};
use crate::signed_field::SignedField;
use crate::{
ast,
ast::Path,
parser::{Item, ItemKind},
};
use crate::{ast, ast::Path, parser::ItemKind};
use fm::FileId;
use noirc_errors::debug_info::{DebugFnId, DebugFunction};
use noirc_errors::{Location, Span};
Expand Down Expand Up @@ -60,13 +56,24 @@ impl Default for DebugInstrumenter {
impl DebugInstrumenter {
pub fn instrument_module(&mut self, module: &mut ParsedModule, file: FileId) {
module.items.iter_mut().for_each(|item| {
if let Item { kind: ItemKind::Function(f), .. } = item {
self.walk_fn(&mut f.def);
match &mut item.kind {
// Instrument top-level functions of a module
ItemKind::Function(f) => self.walk_fn(&mut f.def),
// Instrument contract module
ItemKind::Submodules(ParsedSubModule {
is_contract: true,
contents: contract_module,
..
}) => {
self.instrument_module(contract_module, file);
}
_ => (),
}
});

// this part absolutely must happen after ast traversal above
// so that oracle functions don't get wrapped, resulting in infinite recursion:
self.insert_state_set_oracle(module, 8, file);
self.insert_state_set_oracle(module, file);
}

fn insert_var(&mut self, var_name: &str) -> Option<SourceVarId> {
Expand Down Expand Up @@ -499,8 +506,8 @@ impl DebugInstrumenter {
}
}

fn insert_state_set_oracle(&self, module: &mut ParsedModule, n: u32, file: FileId) {
let member_assigns = (1..=n)
fn insert_state_set_oracle(&self, module: &mut ParsedModule, file: FileId) {
let member_assigns = (1..=MAX_MEMBER_ASSIGN_DEPTH)
.map(|i| format!["__debug_member_assign_{i}"])
.collect::<Vec<String>>()
.join(",\n");
Expand Down
47 changes: 42 additions & 5 deletions docs/docs/how_to/debugger/debugging_with_the_repl.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Using the REPL Debugger
description:
Step-by-step guide on how to debug your Noir circuits with the REPL Debugger.
Step-by-step guide on how to debug your Noir circuits with the REPL Debugger.
keywords:
[
Nargo,
Expand All @@ -14,7 +14,7 @@ sidebar_position: 1

#### Pre-requisites

In order to use the REPL debugger, first you need to install recent enough versions of Nargo and vscode-noir.
In order to use the REPL debugger, first you need to install recent enough versions of Nargo.

## Debugging a simple circuit

Expand All @@ -38,7 +38,7 @@ At ~/noir-examples/recursion/circuits/main/src/main.nr:1:9
1 -> fn main(x : Field, y : pub Field) {
2 assert(x != y);
3 }
>
>
```

The debugger displays the current Noir code location, and it is now waiting for us to drive it.
Expand Down Expand Up @@ -84,7 +84,7 @@ Some commands operate only for unconstrained functions, such as `memory` and `me
```
> memory
Unconstrained VM memory not available
>
>
```

Before continuing, we can take a look at the initial witness map:
Expand Down Expand Up @@ -115,7 +115,7 @@ _1 = 2
>
```

Now we can inspect the current state of local variables. For that we use the `vars` command.
Now we can inspect the current state of local variables. For that we use the `vars` command.

```
> vars
Expand Down Expand Up @@ -162,3 +162,40 @@ Finished execution
Upon quitting the debugger after a solved circuit, the resulting circuit witness gets saved, equivalent to what would happen if we had run the same circuit with `nargo execute`.

We just went through the basics of debugging using Noir REPL debugger. For a comprehensive reference, check out [the reference page](../../reference/debugger/debugger_repl.md).

## Debugging a test function

Let's debug a simple test:

```rust
#[noir]
fn test_simple_equal() {
let x = 2;
let y = 1 + 1;
assert(x == y, "should be equal");
}
```

To debug a test function using the REPL debugger, navigate to a Noir project directory inside a terminal, and run the `nargo debug` command passing the `--test-name your_test_name_here` argument.

```bash
nargo debug --test-name test_simple_equal
```

After that, the debugger has started and works the same as debugging a main function, you can use any of the above explained commands to control the execution of the test function.

### Test result

The debugger does not end the session automatically. Once you finish debugging the execution of the test function you will notice that the debugger remains in the `Execution finished` state. When you are done debugging the test function you can exit the debugger by using the `quit` command. Once you finish the debugging session you should see the test result.

```text
$ nargo debug --test-name test_simple_equal
[simple_noir_project] Starting debugger
At opcode 0:0 :: BRILLIG CALL func 0: inputs: [], outputs: []
> continue
(Continuing execution...)
Finished execution
> quit
[simple_noir_project] Circuit witness successfully solved
[simple_noir_project] Testing test_simple_equal... ok
```
18 changes: 13 additions & 5 deletions docs/docs/how_to/debugger/debugging_with_vs_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ keywords:
sidebar_position: 0
---

This guide will show you how to use VS Code with the vscode-noir extension to debug a Noir project.
This guide will show you how to use VS Code with the vscode-noir extension to debug a Noir project.

#### Pre-requisites

Expand All @@ -23,7 +23,15 @@ This guide will show you how to use VS Code with the vscode-noir extension to de

## Running the debugger

The easiest way to start debugging is to open the file you want to debug, and press `F5`. This will cause the debugger to launch, using your `Prover.toml` file as input.
The easiest way to start debugging is to open the file you want to debug, and click on `Debug` codelens over main functions or `Debug test` over `#[test]` functions

If you don't see the codelens options `Compile|Info|..|Debug` over the `main` function or `Run test| Debug test` over a test function then you probably have the codelens feature disabled. To enable it open the extension configuration page and check the `Enable Code Lens` setting.

![Debugger codelens](@site/static/img/debugger/debugger-codelens.png)

Another way of starting the debugger is to press `F5` on the file you want to debug. This will cause the debugger to launch, using your `Prover.toml` file as input.

Once the debugger has started you should see something like this:

You should see something like this:

Expand All @@ -37,11 +45,11 @@ You will now see two categories of variables: Locals and Witness Map.

![Debug pane expanded](@site/static/img/debugger/3-debug-pane.png)

1. **Locals**: variables of your program. At this point in execution this section is empty, but as we step through the code it will get populated by `x`, `result`, `digest`, etc.
1. **Locals**: variables of your program. At this point in execution this section is empty, but as we step through the code it will get populated by `x`, `result`, `digest`, etc.

2. **Witness map**: these are initially populated from your project's `Prover.toml` file. In this example, they will be used to populate `x` and `result` at the beginning of the `main` function.

Most of the time you will probably be focusing mostly on locals, as they represent the high level state of your program.
Most of the time you will probably be focusing mostly on locals, as they represent the high level state of your program.

You might be interested in inspecting the witness map in case you are trying to solve a really low level issue in the compiler or runtime itself, so this concerns mostly advanced or niche users.

Expand All @@ -57,7 +65,7 @@ We can also inspect the values of variables by directly hovering on them on the

![Hover locals](@site/static/img/debugger/6-hover.png)

Let's set a break point at the `keccak256` function, so we can continue execution up to the point when it's first invoked without having to go one step at a time.
Let's set a break point at the `keccak256` function, so we can continue execution up to the point when it's first invoked without having to go one step at a time.

We just need to click to the right of the line number 18. Once the breakpoint appears, we can click the `continue` button or use its corresponding keyboard shortcut (`F5` by default).

Expand Down
42 changes: 28 additions & 14 deletions docs/docs/reference/debugger/debugger_repl.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: REPL Debugger
description:
Noir Debugger REPL options and commands.
Noir Debugger REPL options and commands.
keywords:
[
Nargo,
Expand All @@ -20,21 +20,30 @@ Runs the Noir REPL debugger. If a `WITNESS_NAME` is provided the debugger writes

### Options

| Option | Description |
| --------------------- | ------------------------------------------------------------ |
| Option | Description |
| --------------------------------- | ----------------------------------------------------------------------------------- |
| `-p, --prover-name <PROVER_NAME>` | The name of the toml file which contains the inputs for the prover [default: Prover]|
| `--package <PACKAGE>` | The name of the package to debug |
| `--print-acir` | Display the ACIR for compiled circuit |
| `--deny-warnings` | Treat all warnings as errors |
| `--silence-warnings` | Suppress warnings |
| `-h, --help` | Print help |
| `--package <PACKAGE>` | The name of the package to debug |
| `--print-acir` | Display the ACIR for compiled circuit |
| `--test-name <TEST_NAME>` | The name (or substring) of the test function to debug |
| `--oracle-resolver <RESOLVER_URL>`| JSON RPC url to solve oracle calls |
| `-h, --help` | Print help |

None of these options are required.

:::note
Since the debugger starts by compiling the target package, all Noir compiler options are also available. Check out the [compiler reference](../nargo_commands.md#nargo-compile) to learn more about the compiler options.
:::

:::note
If the `--test-name` option is provided the debugger will debug the matching function instead of the package `main` function.
This argument must only match one function. If the given name matches with more than one test function the debugger will not start.
:::

:::note
For debugging aztec-contract tests that interact with the TXE ([see further details here](https://docs.aztec.network/developers/guides/smart_contracts/testing)), a JSON RPC server URL must be provided by setting the `--oracle-resolver` option
:::

## REPL commands

Once the debugger is running, it accepts the following commands.
Expand All @@ -53,6 +62,7 @@ Available commands:
out step until a new source location is reached
and the current stack frame is finished
break LOCATION:OpcodeLocation add a breakpoint at an opcode location
break line:i64 add a breakpoint at an opcode associated to the given source code line
over step until a new source location is reached
without diving into function calls
restart restart the debugging session
Expand Down Expand Up @@ -94,7 +104,7 @@ Step until the next Noir source code location. While other commands, such as [`i
```


Using `next` here would cause the debugger to jump to the definition of `deep_entry_point` (if available).
Using `next` here would cause the debugger to jump to the definition of `deep_entry_point` (if available).

If you want to step over `deep_entry_point` and go straight to line 8, use [the `over` command](#over) instead.

Expand Down Expand Up @@ -129,11 +139,11 @@ Step until the end of the current function call. For example:
7 -> assert(deep_entry_point(x) == 4);
8 multiple_values_entry_point(x);
9 }
10
10
11 unconstrained fn returns_multiple_values(x: u32) -> (u32, u32, u32, u32) {
12 ...
...
55
55
56 unconstrained fn deep_entry_point(x: u32) -> u32 {
57 -> level_1(x + 1)
58 }
Expand Down Expand Up @@ -180,7 +190,7 @@ Steps into the next opcode. A compiled Noir program is a sequence of ACIR opcode
...
1.43 | Return
2 EXPR [ (1, _1) -2 ]
```
```

The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start.

Expand Down Expand Up @@ -249,6 +259,10 @@ In this example, issuing a `break 1.2` command adds break on opcode 1.2, as deno

Running [the `continue` command](#continue-c) at this point would cause the debugger to execute the program until opcode 1.2.

#### `break [line]` (or shorthand `b [line]`)

Similar to `break [opcode]`, but instead of selecting the opcode by index selects the opcode location by matching the source code location

#### `delete [Opcode]` (or shorthand `d [Opcode]`)

Deletes a breakpoint at an opcode location. Usage is analogous to [the `break` command](#).
Expand All @@ -260,7 +274,7 @@ Deletes a breakpoint at an opcode location. Usage is analogous to [the `break` c
Show variable values available at this point in execution.

:::note
The ability to inspect variable values from the debugger depends on compilation to be run in a special debug instrumentation mode. This instrumentation weaves variable tracing code with the original source code.
The ability to inspect variable values from the debugger depends on compilation to be run in a special debug instrumentation mode. This instrumentation weaves variable tracing code with the original source code.

So variable value inspection comes at the expense of making the resulting ACIR bytecode bigger and harder to understand and optimize.

Expand Down Expand Up @@ -357,4 +371,4 @@ Update a memory cell with the given value. For example:

:::note
This command is only functional while the debugger is executing unconstrained code.
:::
:::
Loading
Loading