Skip to content

Commit

Permalink
Merge pull request #693 from 0xPolygonMiden/frisitano-simple-debug
Browse files Browse the repository at this point in the history
feat(miden): Assembly debugger
  • Loading branch information
frisitano authored Feb 17, 2023
2 parents cff3623 + 1d96d59 commit 5c96963
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 9 deletions.
40 changes: 32 additions & 8 deletions docs/src/intro/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Currently, Miden VM can be executed with the following subcommands:
* `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution.
* `verify` - this will verify a previously generated proof of execution for a given program.
* `compile` - this will compile a Miden assembly program (i.e., build a program [MAST](../design/programs.md)) and outputs stats about the compilation process.
* `debug` - this will instantiate a CLI debugger against the specified Miden assembly program and inputs.
* `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution.
* `repl` - this will initiate the [Miden REPL](usage.md#repl) tool.

Expand All @@ -60,11 +61,34 @@ If you want the output of the program in a file, you can use the `--output` or `
```
This will dump the output of the program into the `fib.out` file. The output file will contain the state of the stack at the end of the program execution.

## REPL
## Miden Development Tooling

### Miden Debugger

The Miden debugger is a shell that allow for efficient debugging of miden assembly programs that are sourced from file. A Miden assembly program file and inputs are specified when the debugger is instantiated. The debugger allows the user to step through the execution of the program with clock cycle granularity and provides the ability for virtual machine state inspection at each clock cycle. The Miden debugger supports the following commands:

```
!next steps to the next clock cycle
!play executes program until completion or failure
!play.n executes n clock cycles
!prev steps to the previous clock cycle
!rewind rewinds program until beginning
!rewind.n rewinds n clock cycles
!print displays the complete state of the virtual machine
!stack displays the complete state of the stack
!stack[i] displays the stack element at index i
!mem displays the complete state of memory
!mem[i] displays memory at address i
!clock displays the current clock cycle
!quit quits the debugger
!help displays this message
```

### REPL

The Miden Read–eval–print loop (REPL) is a Miden shell that allows for quick and easy debugging of Miden assembly. After the REPL gets initialized, you can execute any Miden instruction, undo executed instructions, check the state of the stack and memory at a given point, and do many other useful things! When the REPL is exited, a `history.txt` file is saved. One thing to note is that all the REPL native commands start with an `!` to differentiate them from regular assembly instructions. The REPL currently supports the following commands:

### Miden assembly instruction
#### Miden assembly instruction

All Miden instructions mentioned in the [Miden Assembly sections](../user_docs/assembly/main.md) are valid. One can either input instructions one by one or multiple instructions in one input.

Expand Down Expand Up @@ -93,11 +117,11 @@ The above example should be written as follows in the REPL tool:
repeat.20 pow2 end
```

### !help
#### !help

The `!help` command prints out all the available commands in the REPL tool.

### !program
#### !program

The `!program` command prints out the entire Miden program being executed. E.g., in the below scenario:

Expand All @@ -114,7 +138,7 @@ begin
end
```

### !stack
#### !stack

The `!stack` command prints out the state of the stack at the last executed instruction. Since the stack always contains at least 16 elements, 16 or more elements will be printed out (even if all of them are zeros).

Expand All @@ -134,7 +158,7 @@ The `!stack` command will print out the following state of the stack:
3072 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
```

### !mem
#### !mem

The `!mem` command prints out the contents of all initialized memory locations. For each such location, the address, along with its memory values, is printed. Recall that four elements are stored at each memory address.

Expand All @@ -154,7 +178,7 @@ If the memory is not yet been initialized:
The memory has not been initialized yet
```

### !mem[addr]
#### !mem[addr]

The `!mem[addr]` command prints out memory contents at the address specified by `addr`.

Expand All @@ -172,7 +196,7 @@ If the `addr` has not been initialized:
Memory at address 87 is empty
```

### !undo
#### !undo

The `!undo` command reverts to the previous state of the stack and memory by dropping off the last executed assembly instruction from the program. One could use `!undo` as often as they want to restore the state of a stack and memory $n$ instructions ago (provided there are $n$ instructions in the program). The `!undo` command will result in an error if no remaining instructions are left in the Miden program.

Expand Down
1 change: 1 addition & 0 deletions miden/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ Currently, Miden VM can be executed with the following subcommands:
* `prove` - this will execute a Miden assembly program, and will also generate a STARK proof of execution.
* `verify` - this will verify a previously generated proof of execution for a given program.
* `compile` - this will compile a Miden assembly program and outputs stats about the compilation process.
* `debug` - this will instantiate a CLI debugger against the specified Miden assembly program and inputs.
* `analyze` - this will run a Miden assembly program against specific inputs and will output stats about its execution.

All of the above subcommands require various parameters to be provided. To get more detailed help on what is needed for a given subcommand, you can run the following:
Expand Down
88 changes: 88 additions & 0 deletions miden/src/cli/debug/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/// debug commands supported by the debugger
pub enum DebugCommand {
PlayAll,
Play(usize),
RewindAll,
Rewind(usize),
PrintState,
PrintStack,
PrintStackItem(usize),
PrintMem,
PrintMemAddress(u64),
Clock,
Quit,
Help,
}

impl DebugCommand {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a new DebugCommand created specified command string.
///
/// # Errors
/// Returns an error if the command cannot be parsed.
pub fn parse(command: &str) -> Result<Self, String> {
match command {
"!next" => Ok(Self::Play(1)),
"!play" => Ok(Self::PlayAll),
"!prev" => Ok(Self::Rewind(1)),
"!rewind" => Ok(Self::RewindAll),
"!print" => Ok(Self::PrintState),
"!mem" => Ok(Self::PrintMem),
"!stack" => Ok(Self::PrintStack),
"!clock" => Ok(Self::Clock),
"!quit" => Ok(Self::Quit),
"!help" => Ok(Self::Help),
x if x.starts_with("!rewind.") => Self::parse_rewind(x),
x if x.starts_with("!play.") => Self::parse_play(command),
x if x.starts_with("!stack[") && x.ends_with("]") => Self::parse_print_stack(x),
x if x.starts_with("!mem[") && x.ends_with(']') => Self::parse_print_memory(x),
_ => {
Err(format!("malformed command - does not match any known command: `{}`", command))
}
}
}

// HELPERS
// --------------------------------------------------------------------------------------------

/// parse play command - !play.num_cycles
fn parse_play(command: &str) -> Result<Self, String> {
// parse number of cycles
let num_cycles = command[6..].parse::<usize>().map_err(|err| {
format!("malformed command - failed to parse number of cycles: `{}` {}", command, err)
})?;

Ok(Self::Play(num_cycles))
}

/// parse rewind command - !rewind.num_cycles
fn parse_rewind(command: &str) -> Result<Self, String> {
// parse number of cycles
let num_cycles = command[8..].parse::<usize>().map_err(|err| {
format!("malformed command - failed to parse number of cycles: `{}` {}", command, err)
})?;

Ok(Self::Rewind(num_cycles))
}

/// parse print memory command - !mem[address]
fn parse_print_memory(command: &str) -> Result<Self, String> {
// parse address
let address = command[5..command.len() - 1].parse::<u64>().map_err(|err| {
format!("malformed command - failed to parse address parameter: `{}` {}", command, err)
})?;

Ok(Self::PrintMemAddress(address))
}

/// parse print stack command - !stack[index]
fn parse_print_stack(command: &str) -> Result<Self, String> {
// parse stack index
let index = command[7..command.len() - 1].parse::<usize>().map_err(|err| {
format!("malformed command - failed to parse stack index: `{}` {}", command, err)
})?;

Ok(Self::PrintStackItem(index))
}
}
203 changes: 203 additions & 0 deletions miden/src/cli/debug/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use super::DebugCommand;
use miden::{
math::{Felt, StarkField},
MemAdviceProvider, Program, StackInputs, VmState, VmStateIterator,
};

/// Holds debugger state and iterator used for debugging.
pub struct DebugExecutor {
vm_state_iter: VmStateIterator,
vm_state: VmState,
}

impl DebugExecutor {
// CONSTRUCTOR
// --------------------------------------------------------------------------------------------
/// Returns a new DebugExecutor for the specified program, inputs and advice provider.
///
/// # Errors
/// Returns an error if the command cannot be parsed.
pub fn new(
program: Program,
stack_inputs: StackInputs,
advice_provider: MemAdviceProvider,
) -> Result<Self, String> {
let mut vm_state_iter = processor::execute_iter(&program, stack_inputs, advice_provider);
let vm_state = vm_state_iter
.next()
.ok_or(format!(
"Failed to instantiate DebugExecutor - `VmStateIterator` is not yielding!"
))?
.expect("initial state of vm must be healthy!");

Ok(Self {
vm_state_iter,
vm_state,
})
}

// MODIFIERS
// --------------------------------------------------------------------------------------------

/// executes a debug command against the vm in it's current state.
pub fn execute(&mut self, command: DebugCommand) -> bool {
match command {
DebugCommand::PlayAll => {
while let Some(new_vm_state) = self.next_vm_state() {
self.vm_state = new_vm_state;
}
self.print_vm_state();
}
DebugCommand::Play(cycles) => {
for _cycle in 0..cycles {
match self.next_vm_state() {
Some(next_vm_state) => {
self.vm_state = next_vm_state;
}
None => break,
}
}
self.print_vm_state();
}
DebugCommand::RewindAll => {
while let Some(new_vm_state) = self.prev_vm_state() {
self.vm_state = new_vm_state;
}
self.print_vm_state();
}
DebugCommand::Rewind(cycles) => {
for _cycle in 0..cycles {
match self.prev_vm_state() {
Some(new_vm_state) => {
self.vm_state = new_vm_state;
}
None => break,
}
}
self.print_vm_state()
}
DebugCommand::PrintState => self.print_vm_state(),
DebugCommand::PrintStack => self.print_stack(),
DebugCommand::PrintStackItem(index) => self.print_stack_item(index),
DebugCommand::PrintMem => self.print_memory(),
DebugCommand::PrintMemAddress(address) => self.print_memory_entry(address),
DebugCommand::Clock => println!("{}", self.vm_state.clk),
DebugCommand::Help => Self::print_help(),
DebugCommand::Quit => return false,
}
true
}

/// iterates to the next clock cycle.
fn next_vm_state(&mut self) -> Option<VmState> {
match self.vm_state_iter.next() {
Some(next_vm_state_result) => match next_vm_state_result {
Ok(vm_state) => Some(vm_state),
Err(err) => {
println!("Execution error: {err:?}");
None
}
},
None => {
println!("Program execution complete.");
None
}
}
}

/// iterates to the previous clock cycle.
fn prev_vm_state(&mut self) -> Option<VmState> {
match self.vm_state_iter.next_back() {
Some(prev_vm_state_result) => prev_vm_state_result.ok(),
None => {
println!("At start of program execution.");
None
}
}
}

// ACCESSORS
// --------------------------------------------------------------------------------------------

/// print general VM state information.
fn print_vm_state(&self) {
println!("{}", self.vm_state)
}

/// print all stack items.
pub fn print_stack(&self) {
println!(
"{}",
self.vm_state
.stack
.iter()
.enumerate()
.map(|(i, f)| format!("[{i}] {f}"))
.collect::<Vec<_>>()
.join("\n"),
)
}

/// print specified stack item.
pub fn print_stack_item(&self, index: usize) {
let len = self.vm_state.stack.len();
println!("stack len {}", len);
if index >= len {
println!("stack index must be < {len}")
} else {
println!("[{index}] = {}", self.vm_state.stack[index])
}
}

/// print all memory entries.
pub fn print_memory(&self) {
for (address, mem) in self.vm_state.memory.iter() {
Self::print_memory_data(address, mem)
}
}

/// print specified memory entry.
pub fn print_memory_entry(&self, address: u64) {
let entry = self.vm_state.memory.iter().find_map(|(addr, mem)| match address == *addr {
true => Some(mem),
false => None,
});

match entry {
Some(mem) => Self::print_memory_data(&address, mem),
None => println!("memory at address '{address}' not found"),
}
}

// HELPERS
// --------------------------------------------------------------------------------------------

/// print memory data.
fn print_memory_data(address: &u64, memory: &[Felt]) {
let mem_int = memory.iter().map(|&x| x.as_int()).collect::<Vec<_>>();
println!("{address} {mem_int:?}");
}

/// print help message
fn print_help() {
let message = "---------------------------------------------------------\n\
Miden Assembly Debug CLI\n\
---------------------------------------------------------\n\
!next steps to the next clock cycle\n\
!play executes program until completion or failure\n\
!play.n executes n clock cycles\n\
!prev steps to the previous clock cycle\n\
!rewind rewinds program until beginning\n\
!rewind.n rewinds n clock cycles\n\
!print displays the complete state of the virtual machine\n\
!stack displays the complete state of the stack\n\
!stack[i] displays the stack element at index i\n\
!mem displays the complete state of memory\n\
!mem[i] displays memory at address i\n\
!clock displays the current clock cycle\n\
!quit quits the debugger\n\
!help displays this message";

println!("{}", message);
}
}
Loading

0 comments on commit 5c96963

Please sign in to comment.