From 11e2554c4fdc82bc70f2e207f8b1eea2273a105b Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Sun, 10 Sep 2023 01:47:27 -0700 Subject: [PATCH] feat: implement basic Debug decorator --- CHANGELOG.md | 1 + assembly/src/assembler/instruction/mod.rs | 9 ++- assembly/src/assembler/span_builder.rs | 3 +- assembly/src/ast/nodes/mod.rs | 3 + assembly/src/ast/nodes/serde/debug.rs | 32 ++++++++++ .../src/ast/nodes/serde/deserialization.rs | 10 +++- assembly/src/ast/nodes/serde/mod.rs | 5 +- assembly/src/ast/nodes/serde/serialization.rs | 7 ++- assembly/src/ast/parsers/context.rs | 9 +-- assembly/src/ast/parsers/debug.rs | 36 ++++++++++++ assembly/src/ast/parsers/mod.rs | 17 +++--- core/src/lib.rs | 3 +- core/src/operations/decorators/debug.rs | 25 ++++++++ core/src/operations/decorators/mod.rs | 50 ++++++++++++---- core/src/operations/mod.rs | 4 +- core/src/program/blocks/span_block.rs | 6 +- docs/src/SUMMARY.md | 1 + docs/src/user_docs/assembly/debugging.md | 10 ++++ miden/Cargo.toml | 2 +- miden/src/cli/run.rs | 2 +- processor/src/decorators/mod.rs | 58 ++++++++++++++++++- processor/src/lib.rs | 14 ++++- processor/src/stack/mod.rs | 9 ++- 23 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 assembly/src/ast/nodes/serde/debug.rs create mode 100644 assembly/src/ast/parsers/debug.rs create mode 100644 core/src/operations/decorators/debug.rs create mode 100644 docs/src/user_docs/assembly/debugging.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 187d3f4442..8063f31b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 161eebe889..66ce420a7c 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -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; @@ -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 diff --git a/assembly/src/assembler/span_builder.rs b/assembly/src/assembler/span_builder.rs index 44aadfdf0b..63db32f057 100644 --- a/assembly/src/assembler/span_builder.rs +++ b/assembly/src/assembler/span_builder.rs @@ -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") } } diff --git a/assembly/src/ast/nodes/mod.rs b/assembly/src/ast/nodes/mod.rs index 2eed169b34..c17661cfa6 100644 --- a/assembly/src/ast/nodes/mod.rs +++ b/assembly/src/ast/nodes/mod.rs @@ -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; @@ -297,6 +298,7 @@ pub enum Instruction { // ----- debug decorators --------------------------------------------------------------------- Breakpoint, + Debug(DebugOptions), } impl Instruction { @@ -573,6 +575,7 @@ impl fmt::Display for Instruction { // ----- debug decorators ------------------------------------------------------------- Self::Breakpoint => write!(f, "breakpoint"), + Self::Debug(options) => write!(f, "debug.{options}"), } } } diff --git a/assembly/src/ast/nodes/serde/debug.rs b/assembly/src/ast/nodes/serde/debug.rs new file mode 100644 index 0000000000..18bcb6a6a7 --- /dev/null +++ b/assembly/src/ast/nodes/serde/debug.rs @@ -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(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( + source: &mut R, +) -> Result { + 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())), + } +} diff --git a/assembly/src/ast/nodes/serde/deserialization.rs b/assembly/src/ast/nodes/serde/deserialization.rs index be0667c230..7e375ee252 100644 --- a/assembly/src/ast/nodes/serde/deserialization.rs +++ b/assembly/src/ast/nodes/serde/deserialization.rs @@ -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 @@ -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 diff --git a/assembly/src/ast/nodes/serde/mod.rs b/assembly/src/ast/nodes/serde/mod.rs index 31653d5b6a..ad04305a59 100644 --- a/assembly/src/ast/nodes/serde/mod.rs +++ b/assembly/src/ast/nodes/serde/mod.rs @@ -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; @@ -273,6 +273,9 @@ pub enum OpCode { CallImported = 238, SysCall = 239, + // ----- debugging ---------------------------------------------------------------------------- + Debug = 240, + // ----- control flow ------------------------------------------------------------------------- IfElse = 253, Repeat = 254, diff --git a/assembly/src/ast/nodes/serde/serialization.rs b/assembly/src/ast/nodes/serde/serialization.rs index e0b33d7e32..1e62fb26c9 100644 --- a/assembly/src/ast/nodes/serde/serialization.rs +++ b/assembly/src/ast/nodes/serde/serialization.rs @@ -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 @@ -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); + } } } } diff --git a/assembly/src/ast/parsers/context.rs b/assembly/src/ast/parsers/context.rs index babc300367..dcfa93cfce 100644 --- a/assembly/src/ast/parsers/context.rs +++ b/assembly/src/ast/parsers/context.rs @@ -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}; @@ -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)), diff --git a/assembly/src/ast/parsers/debug.rs b/assembly/src/ast/parsers/debug.rs new file mode 100644 index 0000000000..ef7158c784 --- /dev/null +++ b/assembly/src/ast/parsers/debug.rs @@ -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 { + debug_assert_eq!(op.parts()[0], "debug"); + if op.num_parts() < 2 { + return Err(ParsingError::missing_param(op, "debug.stack.")); + } + + 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))) +} diff --git a/assembly/src/ast/parsers/mod.rs b/assembly/src/ast/parsers/mod.rs index 3f4a34f250..2ac0a2344b 100644 --- a/assembly/src/ast/parsers/mod.rs +++ b/assembly/src/ast/parsers/mod.rs @@ -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; diff --git a/core/src/lib.rs b/core/src/lib.rs index ff4e088037..0908898053 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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; diff --git a/core/src/operations/decorators/debug.rs b/core/src/operations/decorators/debug.rs new file mode 100644 index 0000000000..b6bdc81334 --- /dev/null +++ b/core/src/operations/decorators/debug.rs @@ -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}"), + } + } +} diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index f34a015173..e743a01b55 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -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 { @@ -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})"), } } } @@ -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) @@ -59,3 +72,16 @@ impl<'a> DecoratorIterator<'a> { } } } + +impl<'a> Iterator for DecoratorIterator<'a> { + type Item = &'a Decorator; + + fn next(&mut self) -> Option { + if self.idx < self.decorators.len() { + self.idx += 1; + Some(&self.decorators[self.idx - 1].1) + } else { + None + } + } +} diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 28eb25c9fa..d16d184987 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -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 // ================================================================================================ diff --git a/core/src/program/blocks/span_block.rs b/core/src/program/blocks/span_block.rs index 570d194439..e9aadf610b 100644 --- a/core/src/program/blocks/span_block.rs +++ b/core/src/program/blocks/span_block.rs @@ -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() { @@ -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" ); } } diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 62a956d442..7b162b78a1 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -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) diff --git a/docs/src/user_docs/assembly/debugging.md b/docs/src/user_docs/assembly/debugging.md new file mode 100644 index 0000000000..173acb4272 --- /dev/null +++ b/docs/src/user_docs/assembly/debugging.md @@ -0,0 +1,10 @@ +# Debugging + +To support basic debugging capabilities, Miden assembly provides a `debug` instruction. This instruction prints out the state of the VM at the time when the `debug` instruction is executed. The instruction can be parameterized as follows: + +- `debug.stack` prints out the entire contents of the stack. +- `debug.stack.` prints out the top $n$ items of the stack. $n$ must be an integer greater than $0$ and smaller than $256$. + +Debug instructions do not affect the VM state and do not change the program hash. + +To make use of the `debug` instruction, programs must be compiled with an assembler instantiated in the debug mode. Otherwise, the assembler will simply ignore the `debug` instructions. \ No newline at end of file diff --git a/miden/Cargo.toml b/miden/Cargo.toml index 61e678b885..487f2d17a9 100644 --- a/miden/Cargo.toml +++ b/miden/Cargo.toml @@ -44,7 +44,7 @@ std = ["assembly/std", "log/std", "processor/std", "prover/std", "verifier/std"] [dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.7", default-features = false } -clap = { version = "3.0", features = ["derive"], optional = true } +clap = { version = "4.0", features = ["derive"], optional = true } env_logger = { version = "0.10", default-features = false, optional = true } hex = { version = "0.4", optional = true } log = { version = "0.4", default-features = false } diff --git a/miden/src/cli/run.rs b/miden/src/cli/run.rs index ee6c4e05c6..ab170aa228 100644 --- a/miden/src/cli/run.rs +++ b/miden/src/cli/run.rs @@ -26,7 +26,7 @@ pub struct RunCmd { #[clap(short = 'm', long = "max-cycles", default_value = "4294967295")] max_cycles: u32, - /// Number of ouptuts + /// Number of outputs #[clap(short = 'n', long = "num-outputs", default_value = "16")] num_outputs: usize, diff --git a/processor/src/decorators/mod.rs b/processor/src/decorators/mod.rs index 98278864fc..dcad55921f 100644 --- a/processor/src/decorators/mod.rs +++ b/processor/src/decorators/mod.rs @@ -1,4 +1,7 @@ -use super::{AdviceInjector, AdviceProvider, AdviceSource, Decorator, ExecutionError, Process}; +use super::{ + AdviceInjector, AdviceProvider, AdviceSource, Decorator, ExecutionError, Felt, Process, Vec, +}; +use vm_core::DebugOptions; mod adv_map_injectors; mod adv_stack_injectors; @@ -25,6 +28,7 @@ where self.decoder.append_asmop(self.system.clk(), assembly_op.clone()); } } + Decorator::Debug(options) => self.dec_debug(options)?, } Ok(()) } @@ -33,7 +37,7 @@ where // -------------------------------------------------------------------------------------------- /// Process the specified advice injector. - pub fn dec_advice(&mut self, injector: &AdviceInjector) -> Result<(), ExecutionError> { + fn dec_advice(&mut self, injector: &AdviceInjector) -> Result<(), ExecutionError> { match injector { AdviceInjector::MerkleNodeMerge => self.merge_merkle_nodes(), AdviceInjector::MerkleNodeToStack => self.copy_merkle_node_to_adv_stack(), @@ -83,4 +87,54 @@ where Ok(()) } + + // DEBUG + // -------------------------------------------------------------------------------------------- + + /// Prints the info about the VM state specified by the provided options to stdout. + fn dec_debug(&self, options: &DebugOptions) -> Result<(), ExecutionError> { + let clk = self.system.clk(); + match options { + DebugOptions::StackAll => { + let stack = self.stack.get_state_at(clk); + let n = stack.len(); + print_vm_stack(clk, stack, n); + } + DebugOptions::StackTop(n) => { + let stack = self.stack.get_state_at(clk); + print_vm_stack(clk, stack, *n as usize); + } + } + Ok(()) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +#[cfg(feature = "std")] +fn print_vm_stack(clk: u32, stack: Vec, n: usize) { + // determine how many items to print out + let num_items = core::cmp::min(stack.len(), n); + + // print all items except for the last one + println!("Stack state before step {clk}:"); + for (i, element) in stack.iter().take(num_items - 1).enumerate() { + println!("├── {i:>2}: {element}"); + } + + // print the last item, and in case the stack has more items, print the total number of + // un-printed items + let i = num_items - 1; + if num_items == stack.len() { + println!("└── {i:>2}: {}", stack[i]); + } else { + println!("├── {i:>2}: {}", stack[i]); + println!("└── ({} more items)", stack.len() - num_items); + } +} + +#[cfg(not(feature = "std"))] +fn print_vm_stack(_clk: u32, _stack: Vec, _n: usize) { + // in no_std environments, this is a NOOP } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index dbee423758..9d0e85e9e4 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -377,7 +377,17 @@ where op_offset += op_batch.ops().len(); } - self.end_span_block(block) + self.end_span_block(block)?; + + // execute any decorators which have not been executed during span ops execution; this + // can happen for decorators appearing after all operations in a block. these decorators + // are executed after SPAN block is closed to make sure the VM clock cycle advances beyond + // the last clock cycle of the SPAN block ops. + if let Some(decorator) = decorators.next() { + self.execute_decorator(decorator)?; + } + + Ok(()) } /// Executes all operations in an [OpBatch]. This also ensures that all alignment rules are @@ -405,7 +415,7 @@ where // execute operations in the batch one by one for (i, &op) in batch.ops().iter().enumerate() { - while let Some(decorator) = decorators.next(i + op_offset) { + while let Some(decorator) = decorators.next_filtered(i + op_offset) { self.execute_decorator(decorator)?; } diff --git a/processor/src/stack/mod.rs b/processor/src/stack/mod.rs index fce8cf00e5..bbfee3de17 100644 --- a/processor/src/stack/mod.rs +++ b/processor/src/stack/mod.rs @@ -125,11 +125,16 @@ impl Stack { /// stack + overflow entries. /// /// # Panics - /// Panics if invoked on a stack instantiated with `keep_overflow_trace` set to false. + /// Panics if invoked for non-last clock cycle on a stack instantiated with + /// `keep_overflow_trace` set to false. pub fn get_state_at(&self, clk: u32) -> Vec { let mut result = Vec::with_capacity(self.active_depth); self.trace.append_state_into(&mut result, clk); - self.overflow.append_state_into(&mut result, clk as u64); + if clk == self.clk { + self.overflow.append_into(&mut result); + } else { + self.overflow.append_state_into(&mut result, clk as u64); + } result }