Skip to content

Commit

Permalink
Merge pull request #1069 from 0xPolygonMiden/bobbin-debug-decorator
Browse files Browse the repository at this point in the history
Implement basic Debug decorator
  • Loading branch information
bobbinth authored Sep 14, 2023
2 parents 319334d + 11e2554 commit 4075c09
Show file tree
Hide file tree
Showing 23 changed files with 273 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added support for module aliases (#1037).
- Added `adv.insert_hperm` decorator (#1042).
- Added `adv.push_smtpeek` decorator (#1056).
- Added `debug` decorator (#1069).

#### VM Internals
- Simplified range checker and removed 1 main and 1 auxiliary trace column (#949).
Expand Down
9 changes: 8 additions & 1 deletion assembly/src/assembler/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{
};
use crate::utils::bound_into_included_u64;
use core::ops::RangeBounds;
use vm_core::{FieldElement, StarkField};
use vm_core::{Decorator, FieldElement, StarkField};

mod adv_ops;
mod crypto_ops;
Expand Down Expand Up @@ -314,6 +314,13 @@ impl Assembler {
}
Ok(None)
}

Instruction::Debug(options) => {
if self.in_debug_mode() {
span.push_decorator(Decorator::Debug(*options))
}
Ok(None)
}
};

// compute and update the cycle count of the instruction which just finished executing
Expand Down
3 changes: 2 additions & 1 deletion assembly/src/assembler/span_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ impl SpanBuilder {
} else if !self.decorators.is_empty() {
// this is a bug in the assembler. we shouldn't have decorators added without their
// associated operations
unreachable!()
// TODO: change this to an error or allow decorators in empty span blocks
unreachable!("decorators in an empty SPAN block")
}
}

Expand Down
3 changes: 3 additions & 0 deletions assembly/src/ast/nodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::{
AstFormatterContext, CodeBody, Felt, FormattableCodeBody, ProcedureId, RpoDigest, ToString, Vec,
};
use core::fmt;
use vm_core::DebugOptions;

mod advice;
pub use advice::AdviceInjectorNode;
Expand Down Expand Up @@ -297,6 +298,7 @@ pub enum Instruction {

// ----- debug decorators ---------------------------------------------------------------------
Breakpoint,
Debug(DebugOptions),
}

impl Instruction {
Expand Down Expand Up @@ -573,6 +575,7 @@ impl fmt::Display for Instruction {

// ----- debug decorators -------------------------------------------------------------
Self::Breakpoint => write!(f, "breakpoint"),
Self::Debug(options) => write!(f, "debug.{options}"),
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions assembly/src/ast/nodes/serde/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use super::{super::DebugOptions, ByteReader, ByteWriter, DeserializationError, ToString};

const STACK_ALL: u8 = 0;
const STACK_TOP: u8 = 1;

/// Writes the provided [DebugOptions] into the provided target.
pub fn write_options_into<W: ByteWriter>(target: &mut W, options: &DebugOptions) {
match options {
DebugOptions::StackAll => target.write_u8(STACK_ALL),
DebugOptions::StackTop(n) => {
target.write_u8(STACK_TOP);
target.write_u16(*n);
}
}
}

/// Reads [DebugOptions] from the provided source.
pub fn read_options_from<R: ByteReader>(
source: &mut R,
) -> Result<DebugOptions, DeserializationError> {
match source.read_u8()? {
STACK_ALL => Ok(DebugOptions::StackAll),
STACK_TOP => {
let n = source.read_u16()?;
if n == 0 {
return Err(DeserializationError::InvalidValue(n.to_string()));
}
Ok(DebugOptions::StackTop(n))
}
val => Err(DeserializationError::InvalidValue(val.to_string())),
}
}
10 changes: 8 additions & 2 deletions assembly/src/ast/nodes/serde/deserialization.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
super::AdviceInjectorNode, ByteReader, CodeBody, Deserializable, DeserializationError, Felt,
Instruction, Node, OpCode, ProcedureId, RpoDigest, ToString, MAX_PUSH_INPUTS,
super::AdviceInjectorNode, debug, ByteReader, CodeBody, Deserializable, DeserializationError,
Felt, Instruction, Node, OpCode, ProcedureId, RpoDigest, ToString, MAX_PUSH_INPUTS,
};

// NODE DESERIALIZATION
Expand Down Expand Up @@ -355,6 +355,12 @@ impl Deserializable for Instruction {
OpCode::CallImported => Ok(Instruction::CallImported(ProcedureId::read_from(source)?)),
OpCode::SysCall => Ok(Instruction::SysCall(ProcedureId::read_from(source)?)),

// ----- debugging --------------------------------------------------------------------
OpCode::Debug => {
let options = debug::read_options_from(source)?;
Ok(Instruction::Debug(options))
}

// ----- control flow -----------------------------------------------------------------
// control flow instructions should be parsed as a part of Node::read_from() and we
// should never get here
Expand Down
5 changes: 4 additions & 1 deletion assembly/src/ast/nodes/serde/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::{CodeBody, Felt, Instruction, Node, ProcedureId, RpoDigest, ToString};
use crate::MAX_PUSH_INPUTS;
use num_enum::TryFromPrimitive;

use vm_core::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};

mod debug;
mod deserialization;
mod serialization;

Expand Down Expand Up @@ -273,6 +273,9 @@ pub enum OpCode {
CallImported = 238,
SysCall = 239,

// ----- debugging ----------------------------------------------------------------------------
Debug = 240,

// ----- control flow -------------------------------------------------------------------------
IfElse = 253,
Repeat = 254,
Expand Down
7 changes: 6 additions & 1 deletion assembly/src/ast/nodes/serde/serialization.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{ByteWriter, Instruction, Node, OpCode, Serializable};
use super::{debug, ByteWriter, Instruction, Node, OpCode, Serializable};
use crate::ast::MAX_BODY_LEN;

// NODE SERIALIZATION
Expand Down Expand Up @@ -496,6 +496,11 @@ impl Serializable for Instruction {
Self::Breakpoint => {
// this is a transparent instruction and will not be encoded into the library
}

Self::Debug(options) => {
OpCode::Debug.write_into(target);
debug::write_options_into(target, options);
}
}
}
}
9 changes: 5 additions & 4 deletions assembly/src/ast/parsers/context.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::{
super::ProcReExport, adv_ops, field_ops, io_ops, stack_ops, u32_ops, CodeBody, Instruction,
InvocationTarget, LibraryPath, LocalConstMap, LocalProcMap, ModuleImports, Node, ParsingError,
ProcedureAst, ProcedureId, ProcedureName, ReExportedProcMap, Token, TokenStream, MAX_BODY_LEN,
MAX_DOCS_LEN,
super::ProcReExport, adv_ops, debug, field_ops, io_ops, stack_ops, u32_ops, CodeBody,
Instruction, InvocationTarget, LibraryPath, LocalConstMap, LocalProcMap, ModuleImports, Node,
ParsingError, ProcedureAst, ProcedureId, ProcedureName, ReExportedProcMap, Token, TokenStream,
MAX_BODY_LEN, MAX_DOCS_LEN,
};
use vm_core::utils::{collections::Vec, string::ToString};

Expand Down Expand Up @@ -620,6 +620,7 @@ impl ParserContext<'_> {

// ----- debug decorators -------------------------------------------------------------
"breakpoint" => simple_instruction(op, Breakpoint),
"debug" => debug::parse_debug(op),

// ----- catch all --------------------------------------------------------------------
_ => Err(ParsingError::invalid_op(op)),
Expand Down
36 changes: 36 additions & 0 deletions assembly/src/ast/parsers/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::{
parse_checked_param,
Instruction::*,
Node::{self, Instruction},
ParsingError, Token,
};
use vm_core::DebugOptions;

// INSTRUCTION PARSERS
// ================================================================================================

/// Returns `Debug` instruction node.
///
/// # Errors
/// Returns an error if the instruction token contains a wrong number of parameters, or if
/// the provided parameters are not valid.
pub fn parse_debug(op: &Token) -> Result<Node, ParsingError> {
debug_assert_eq!(op.parts()[0], "debug");
if op.num_parts() < 2 {
return Err(ParsingError::missing_param(op, "debug.stack.<debug_params?>"));
}

let options = match op.parts()[1] {
"stack" => match op.num_parts() {
2 => DebugOptions::StackAll,
3 => {
let n: u16 = parse_checked_param(op, 2, 1..=u16::MAX)?;
DebugOptions::StackTop(n)
}
_ => return Err(ParsingError::extra_param(op)),
},
_ => return Err(ParsingError::invalid_op(op)),
};

Ok(Instruction(Debug(options)))
}
17 changes: 9 additions & 8 deletions assembly/src/ast/parsers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ use super::{
};
use core::{fmt::Display, ops::RangeBounds};

pub mod adv_ops;
pub mod field_ops;
pub mod io_ops;
pub mod stack_ops;
pub mod u32_ops;

pub mod constants;
pub use constants::calculate_const_value;
mod adv_ops;
mod debug;
mod field_ops;
mod io_ops;
mod stack_ops;
mod u32_ops;

mod constants;
use constants::calculate_const_value;

mod context;
pub use context::ParserContext;
Expand Down
3 changes: 2 additions & 1 deletion core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ pub use program::{blocks as code_blocks, CodeBlockTable, Kernel, Program, Progra

mod operations;
pub use operations::{
AdviceInjector, AssemblyOp, Decorator, DecoratorIterator, DecoratorList, Operation,
AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList,
Operation,
};

pub mod stack;
Expand Down
25 changes: 25 additions & 0 deletions core/src/operations/decorators/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use core::fmt;

// DEBUG OPTIONS
// ================================================================================================

/// Options of the `Debug` decorator.
///
/// These options define the debug info which gets printed out when the Debug decorator is
/// executed.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DebugOptions {
/// Print out the entire contents of the stack for the current execution context.
StackAll,
/// Prints out the top n items of the stack for the current context.
StackTop(u16),
}

impl fmt::Display for DebugOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StackAll => write!(f, "stack"),
Self::StackTop(n) => write!(f, "stack.{n}"),
}
}
}
50 changes: 38 additions & 12 deletions core/src/operations/decorators/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
mod advice;
mod assembly_op;
use crate::utils::collections::Vec;
use core::fmt;

mod advice;
pub use advice::AdviceInjector;

mod assembly_op;
pub use assembly_op::AssemblyOp;
use core::fmt;

mod debug;
pub use debug::DebugOptions;

// DECORATORS
// ================================================================================================

/// A set of decorators which can be executed by the VM.
///
/// Executing a decorator does not affect the state of the main VM components such as operand stack
/// and memory. However, decorators may modify the advice provider.
///
/// Executing decorators does not advance the VM clock. As such, many decorators can be executed in
/// a single VM cycle.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Decorator {
/// Pushes zero or more values onto the advice stack, as specified by the injector. This
/// operation affects only the advice stack and has no effect on other VM components (e.g.
/// operand stack, memory), and does not advance the VM clock.
/// Injects new data into the advice provider, as specified by the injector.
Advice(AdviceInjector),
/// Adds information about the assembly instruction at a particular index
/// (only applicable in debug mode)
/// Adds information about the assembly instruction at a particular index (only applicable in
/// debug mode).
AsmOp(AssemblyOp),
/// Prints out information about the state of the VM based on the specified options. This
/// decorator is executed only in debug mode.
Debug(DebugOptions),
}

impl fmt::Display for Decorator {
Expand All @@ -26,6 +39,7 @@ impl fmt::Display for Decorator {
Self::AsmOp(assembly_op) => {
write!(f, "asmOp({}, {})", assembly_op.op(), assembly_op.num_cycles())
}
Self::Debug(options) => write!(f, "debug({options})"),
}
}
}
Expand All @@ -46,11 +60,10 @@ impl<'a> DecoratorIterator<'a> {
Self { decorators, idx: 0 }
}

/// Returns the next decorator at the specified position.
/// - Returns the decorator if a decorator at the specified position exists and increments the internal pointer.
/// - Returns None if no decorator is to be executed at the specified position.
/// Returns the next decorator but only if its position matches the specified position,
/// otherwise, None is returned.
#[inline(always)]
pub fn next(&mut self, pos: usize) -> Option<&Decorator> {
pub fn next_filtered(&mut self, pos: usize) -> Option<&Decorator> {
if self.idx < self.decorators.len() && self.decorators[self.idx].0 == pos {
self.idx += 1;
Some(&self.decorators[self.idx - 1].1)
Expand All @@ -59,3 +72,16 @@ impl<'a> DecoratorIterator<'a> {
}
}
}

impl<'a> Iterator for DecoratorIterator<'a> {
type Item = &'a Decorator;

fn next(&mut self) -> Option<Self::Item> {
if self.idx < self.decorators.len() {
self.idx += 1;
Some(&self.decorators[self.idx - 1].1)
} else {
None
}
}
}
4 changes: 3 additions & 1 deletion core/src/operations/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::Felt;
use core::fmt;
mod decorators;
pub use decorators::{AdviceInjector, AssemblyOp, Decorator, DecoratorIterator, DecoratorList};
pub use decorators::{
AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList,
};

// OPERATIONS
// ================================================================================================
Expand Down
6 changes: 3 additions & 3 deletions core/src/program/blocks/span_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ pub fn get_span_op_group_count(op_batches: &[OpBatch]) -> usize {

/// Checks if a given decorators list is valid (only checked in debug mode)
/// - Assert the decorator list is in ascending order.
/// - Assert the last op index in decorator list is less than the number of operations.
/// - Assert the last op index in decorator list is less than or equal to the number of operations.
#[cfg(debug_assertions)]
fn validate_decorators(operations: &[Operation], decorators: &DecoratorList) {
if !decorators.is_empty() {
Expand All @@ -400,8 +400,8 @@ fn validate_decorators(operations: &[Operation], decorators: &DecoratorList) {
}
// assert the last index in decorator list is less than operations vector length
debug_assert!(
operations.len() > decorators.last().expect("empty decorators list").0,
"last op index in decorator list should be less than number of ops"
operations.len() >= decorators.last().expect("empty decorators list").0,
"last op index in decorator list should be less than or equal to the number of ops"
);
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [Stack manipulation](./user_docs/assembly/stack_manipulation.md)
- [Input / Output Operations](./user_docs/assembly/io_operations.md)
- [Cryptographic Operations](./user_docs/assembly/cryptographic_operations.md)
- [Debugging](./user_docs/assembly/debugging.md)
- [Miden Standard Library](./user_docs/stdlib/main.md)
- [std::collections](./user_docs/stdlib/collections.md)
- [std::crypto::fri](./user_docs/stdlib/crypto/fri.md)
Expand Down
Loading

0 comments on commit 4075c09

Please sign in to comment.