Skip to content

Commit

Permalink
chore(debugger): Docs (#4145)
Browse files Browse the repository at this point in the history
# Description

Adds documentation for the Noir debugger.

## Problem

Part of #3015.

## Summary

Adds quickstart, how to's and reference pages to Noir's docsite covering
both the VS Code and REPL debuggers.

## Documentation

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
mverzilli authored Apr 12, 2024
1 parent e412e6e commit 21f9f6f
Show file tree
Hide file tree
Showing 24 changed files with 777 additions and 42 deletions.
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"Maddiaa",
"mathbb",
"memfs",
"memset",
"merkle",
"metas",
"minreq",
Expand Down
38 changes: 0 additions & 38 deletions docs/docs/getting_started/tooling/index.mdx

This file was deleted.

6 changes: 6 additions & 0 deletions docs/docs/how_to/debugger/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"label": "Debugging",
"position": 5,
"collapsible": true,
"collapsed": true
}
164 changes: 164 additions & 0 deletions docs/docs/how_to/debugger/debugging_with_the_repl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
title: Using the REPL Debugger
description:
Step by step guide on how to debug your Noir circuits with the REPL Debugger.
keywords:
[
Nargo,
Noir CLI,
Noir Debugger,
REPL,
]
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.

## Debugging a simple circuit

Let's debug a simple circuit:

```rust
fn main(x : Field, y : pub Field) {
assert(x != y);
}
```

To start the REPL debugger, using a terminal, go to a Noir circuit's home directory. Then:

`$ nargo debug`

You should be seeing this in your terminal:

```
[main] Starting debugger
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.

Let's first take a look at the available commands. For that we'll use the `help` command.

```
> help
Available commands:
opcodes display ACIR opcodes
into step into to the next opcode
next step until a new source location is reached
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
over step until a new source location is reached
without diving into function calls
restart restart the debugging session
delete LOCATION:OpcodeLocation delete breakpoint at an opcode location
witness show witness map
witness index:u32 display a single witness from the witness map
witness index:u32 value:String update a witness with the given value
memset index:usize value:String update a memory cell with the given
value
continue continue execution until the end of the
program
vars show variable values available at this point
in execution
stacktrace display the current stack trace
memory show memory (valid when executing unconstrained code)
step step to the next ACIR opcode
Other commands:
help Show this help message
quit Quit repl
```

Some commands operate only for unconstrained functions, such as `memory` and `memset`. If you try to use them while execution is paused at an ACIR opcode, the debugger will simply inform you that you are not executing unconstrained code:

```
> memory
Unconstrained VM memory not available
>
```

Before continuing, we can take a look at the initial witness map:

```
> witness
_0 = 1
_1 = 2
>
```

Cool, since `x==1`, `y==2`, and we want to check that `x != y`, our circuit should succeed. At this point we could intervene and use the witness setter command to change one of the witnesses. Let's set `y=3`, then back to 2, so we don't affect the expected result:

```
> witness
_0 = 1
_1 = 2
> witness 1 3
_1 = 3
> witness
_0 = 1
_1 = 3
> witness 1 2
_1 = 2
> witness
_0 = 1
_1 = 2
>
```

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

```
> vars
>
```

We currently have no vars in context, since we are at the entry point of the program. Let's use `next` to execute until the next point in the program.

```
> vars
> next
At ~/noir-examples/recursion/circuits/main/src/main.nr:1:20
1 -> fn main(x : Field, y : pub Field) {
2 assert(x != y);
3 }
> vars
x:Field = 0x01
```

As a result of stepping, the variable `x`, whose initial value comes from the witness map, is now in context and returned by `vars`.

```
> next
1 fn main(x : Field, y : pub Field) {
2 -> assert(x != y);
3 }
> vars
y:Field = 0x02
x:Field = 0x01
```

Stepping again we can finally see both variables and their values. And now we can see that the next assertion should succeed.

Let's continue to the end:

```
> continue
(Continuing execution...)
Finished execution
> q
[main] Circuit witness successfully solved
```

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).
68 changes: 68 additions & 0 deletions docs/docs/how_to/debugger/debugging_with_vs_code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Using the VS Code Debugger
description:
Step by step guide on how to debug your Noir circuits with the VS Code Debugger configuration and features.
keywords:
[
Nargo,
Noir CLI,
Noir Debugger,
VS Code,
IDE,
]
sidebar_position: 0
---

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

#### Pre-requisites

- Nargo
- vscode-noir
- A Noir project with a `Nargo.toml`, `Prover.toml` and at least one Noir (`.nr`) containing an entry point function (typically `main`).

## 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.

You should see something like this:

![Debugger launched](@site/static/img/debugger/1-started.png)

Let's inspect the state of the program. For that, we open VS Code's _Debug pane_. Look for this icon:

![Debug pane icon](@site/static/img/debugger/2-icon.png)

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.

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.

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.

Let's step through the program, by using the debugger buttons or their corresponding keyboard shortcuts.

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

Now we can see in the variables pane that there's values for `digest`, `result` and `x`.

![Inspecting locals](@site/static/img/debugger/5-assert.png)

We can also inspect the values of variables by directly hovering on them on the code.

![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.

We just need to click the 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).

![Breakpoint](@site/static/img/debugger/7-break.png)

Now we are debugging the `keccak256` function, notice the _Call Stack pane_ at the lower right. This lets us inspect the current call stack of our process.

That covers most of the current debugger functionalities. Check out [the reference](../../reference/debugger/debugger_vscode.md) for more details on how to configure the debugger.
1 change: 1 addition & 0 deletions docs/docs/how_to/merkle-proof.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ description:
merkle tree with a specified root, at a given index.
keywords:
[merkle proof, merkle membership proof, Noir, rust, hash function, Pedersen, sha256, merkle tree]
sidebar_position: 4
---

Let's walk through an example of a merkle membership proof in Noir that proves that a given leaf is
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/noir/concepts/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn main(x : [Field]) // can't compile, has variable size
fn main(....// i think you got it by now
```

Keep in mind [tests](../../getting_started/tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove:
Keep in mind [tests](../../tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove:

```rust
fn main(x : [Field]) {
Expand Down Expand Up @@ -190,7 +190,7 @@ Supported attributes include:
- **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function`
- **field**: Used to enable conditional compilation of code depending on the field size. See below for more details
- **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details.
- **test**: mark the function as unit tests. See [Tests](../../getting_started/tooling/testing.md) for more details
- **test**: mark the function as unit tests. See [Tests](../../tooling/testing.md) for more details

### Field Attribute

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"position": 2,
"label": "Tooling",
"label": "Debugger",
"position": 1,
"collapsible": true,
"collapsed": true
}
59 changes: 59 additions & 0 deletions docs/docs/reference/debugger/debugger_known_limitations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Known limitations
description:
An overview of known limitations of the current version of the Noir debugger
keywords:
[
Nargo,
Noir Debugger,
VS Code,
]
sidebar_position: 2
---

# Debugger Known Limitations

There are currently some limits to what the debugger can observe.

## Mutable references

The debugger is currently blind to any state mutated via a mutable reference. For example, in:

```
let mut x = 1;
let y = &mut x;
*y = 2;
```

The update on `x` will not be observed by the debugger. That means, when running `vars` from the debugger REPL, or inspecting the _local variables_ pane in the VS Code debugger, `x` will appear with value 1 despite having executed `*y = 2;`.

## Variables of type function or mutable references are opaque

When inspecting variables, any variable of type `Function` or `MutableReference` will render its value as `<<function>>` or `<<mutable ref>>`.

## Debugger instrumentation affects resulting ACIR
In order to make the state of local variables observable, the debugger compiles Noir circuits interleaving foreign calls that track any mutations to them. While this works (except in the cases described above) and doesn't introduce any behavior changes, it does as a side effect produce bigger bytecode. In particular, when running the command `opcodes` on the REPL debugger, you will notice Unconstrained VM blocks that look like this:

```
...
5 BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [], q_c: 2 }), Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(2))], q_c: 0 })]
| outputs=[]
5.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) }
5.1 | Mov { destination: RegisterIndex(3), source: RegisterIndex(1) }
5.2 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } }
5.3 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } }
5.4 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) }
5.5 | Mov { destination: RegisterIndex(3), source: RegisterIndex(3) }
5.6 | Call { location: 8 }
5.7 | Stop
5.8 | ForeignCall { function: "__debug_var_assign", destinations: [], inputs: [RegisterIndex(RegisterIndex(2)), RegisterIndex(RegisterIndex(3))] }
...
```
If you are interested in debugging/inspecting compiled ACIR without these synthetic changes, you can invoke the REPL debugger with the `--skip-instrumentation` flag or launch the VS Code debugger with the `skipConfiguration` property set to true in its launch configuration. You can find more details about those in the [Debugger REPL reference](debugger_repl.md) and the [VS Code Debugger reference](debugger_vscode.md).

:::note
Skipping debugger instrumentation means you won't be able to inspect values of local variables.
:::

Loading

0 comments on commit 21f9f6f

Please sign in to comment.