From 1ad81c842b95559ea5c48987a2417f2e6daa712e Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Mon, 6 Mar 2023 20:25:45 +0100 Subject: [PATCH] feat: add breakpoint instruction This commit introduces `breakpoint`, a transparent instruction that will cause a debug execution to break when reached. This instruction will not be serialized into libraries, and will not have an opcode or be part of the code block tree. For debug executions, it will produce a `NOOP`, so the internal clock of the VM will be affected. The decision of not including this as part of the code block is to avoid further consuming variants of opcodes as they are already scarce. related issue: #580 --- assembly/src/assembler/context.rs | 16 +- assembly/src/assembler/instruction/mod.rs | 11 +- assembly/src/assembler/span_builder.rs | 12 +- assembly/src/parsers/context.rs | 3 + assembly/src/parsers/nodes.rs | 13 ++ assembly/src/parsers/serde/serialization.rs | 5 + core/src/operations/decorators/assembly_op.rs | 42 ++++- miden/src/cli/debug/command.rs | 10 +- miden/src/cli/debug/executor.rs | 33 ++-- miden/tests/integration/exec_iters.rs | 62 ++++++-- .../operations/decorators/asmop.rs | 147 +++++++++++++---- processor/src/debug.rs | 150 ++++++++++-------- processor/src/lib.rs | 3 +- 13 files changed, 365 insertions(+), 142 deletions(-) diff --git a/assembly/src/assembler/context.rs b/assembly/src/assembler/context.rs index 786d688ffb..84bfc61000 100644 --- a/assembly/src/assembler/context.rs +++ b/assembly/src/assembler/context.rs @@ -250,11 +250,18 @@ impl AssemblyContext { // HELPER METHODS // -------------------------------------------------------------------------------------------- - /// Returns the context of the procedure currently being complied, or None if module or + /// Returns the context of the procedure currently being compiled, or None if module or /// procedure stacks are empty. fn current_proc_context(&self) -> Option<&ProcedureContext> { self.module_stack.last().and_then(|m| m.proc_stack.last()) } + + /// Returns the name of the current procedure, or the reserved name for the main block. + pub(crate) fn current_context_name(&self) -> &str { + self.current_proc_context() + .map(|p| p.name().as_ref()) + .unwrap_or(ProcedureName::MAIN_PROC_NAME) + } } // MODULE CONTEXT @@ -478,6 +485,13 @@ impl ProcedureContext { self.name.is_main() } + /// Returns the current context name. + /// + /// Check [AssemblyContext::current_context_name] for reference. + pub const fn name(&self) -> &ProcedureName { + &self.name + } + pub fn into_procedure(self, id: ProcedureId, code_root: CodeBlock) -> Procedure { let Self { name, diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 25eadb7b64..6a874f79d2 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -34,7 +34,7 @@ impl Assembler { // this will allow us to map the instruction to the sequence of operations which were // executed as a part of this instruction. if self.in_debug_mode() { - span.track_instruction(instruction); + span.track_instruction(instruction, ctx); } let result = match instruction { @@ -306,6 +306,15 @@ impl Assembler { Instruction::CallLocal(idx) => self.call_local(*idx, ctx), Instruction::CallImported(id) => self.call_imported(id, ctx), Instruction::SysCall(id) => self.syscall(id, ctx), + + // ----- debug decorators ------------------------------------------------------------- + Instruction::Breakpoint => { + if self.in_debug_mode() { + span.add_op(Noop)?; + span.track_instruction(instruction, ctx); + } + 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 bcc8ac0029..6f1f797e6c 100644 --- a/assembly/src/assembler/span_builder.rs +++ b/assembly/src/assembler/span_builder.rs @@ -1,6 +1,6 @@ use super::{ - AssemblyError, BodyWrapper, Borrow, CodeBlock, Decorator, DecoratorList, Instruction, - Operation, ToString, Vec, + AssemblyContext, AssemblyError, BodyWrapper, Borrow, CodeBlock, Decorator, DecoratorList, + Instruction, Operation, ToString, Vec, }; use vm_core::AssemblyOp; @@ -102,8 +102,12 @@ impl SpanBuilder { /// /// This indicates that the provided instruction should be tracked and the cycle count for /// this instruction will be computed when the call to set_instruction_cycle_count() is made. - pub fn track_instruction(&mut self, instruction: &Instruction) { - let op = AssemblyOp::new(instruction.to_string(), 0); + pub fn track_instruction(&mut self, instruction: &Instruction, ctx: &mut AssemblyContext) { + let context_name = ctx.current_context_name().to_string(); + let num_cycles = 0; + let op = instruction.to_string(); + let should_break = instruction.should_break(); + let op = AssemblyOp::new(context_name, num_cycles, op, should_break); self.push_decorator(Decorator::AsmOp(op)); self.last_asmop_pos = self.decorators.len() - 1; } diff --git a/assembly/src/parsers/context.rs b/assembly/src/parsers/context.rs index ca5972709f..3cb2e9e0ac 100644 --- a/assembly/src/parsers/context.rs +++ b/assembly/src/parsers/context.rs @@ -511,6 +511,9 @@ impl ParserContext { // ----- constant statements ---------------------------------------------------------- "const" => Err(ParsingError::const_invalid_scope(op)), + // ----- debug decorators ------------------------------------------------------------- + "breakpoint" => simple_instruction(op, Breakpoint), + // ----- catch all -------------------------------------------------------------------- _ => Err(ParsingError::invalid_op(op)), } diff --git a/assembly/src/parsers/nodes.rs b/assembly/src/parsers/nodes.rs index 9884e1d750..28d2ecd42f 100644 --- a/assembly/src/parsers/nodes.rs +++ b/assembly/src/parsers/nodes.rs @@ -277,6 +277,16 @@ pub enum Instruction { CallLocal(u16), CallImported(ProcedureId), SysCall(ProcedureId), + + // ----- debug decorators --------------------------------------------------------------------- + Breakpoint, +} + +impl Instruction { + /// Returns true if the instruction should yield a breakpoint. + pub const fn should_break(&self) -> bool { + matches!(self, Self::Breakpoint) + } } impl fmt::Display for Instruction { @@ -543,6 +553,9 @@ impl fmt::Display for Instruction { Self::CallLocal(index) => write!(f, "call.{index}"), Self::CallImported(proc_id) => write!(f, "call.{proc_id}"), Self::SysCall(proc_id) => write!(f, "syscall.{proc_id}"), + + // ----- debug decorators ------------------------------------------------------------- + Self::Breakpoint => write!(f, "breakpoint"), } } } diff --git a/assembly/src/parsers/serde/serialization.rs b/assembly/src/parsers/serde/serialization.rs index 3a6eaed5b2..e0e9b413b3 100644 --- a/assembly/src/parsers/serde/serialization.rs +++ b/assembly/src/parsers/serde/serialization.rs @@ -467,6 +467,11 @@ impl Serializable for Instruction { OpCode::SysCall.write_into(target)?; imported.write_into(target)? } + + // ----- debug decorators ------------------------------------------------------------- + Self::Breakpoint => { + // this is a transparent instruction and will not be encoded into the library + } } Ok(()) } diff --git a/core/src/operations/decorators/assembly_op.rs b/core/src/operations/decorators/assembly_op.rs index 05652a1c6f..1ade86fb22 100644 --- a/core/src/operations/decorators/assembly_op.rs +++ b/core/src/operations/decorators/assembly_op.rs @@ -1,4 +1,5 @@ use crate::utils::string::String; +use core::fmt; // ASSEMBLY OP // ================================================================================================ @@ -6,27 +7,44 @@ use crate::utils::string::String; /// Contains information corresponding to an assembly instruction (only applicable in debug mode). #[derive(Clone, Debug, Eq, PartialEq)] pub struct AssemblyOp { - op: String, + context_name: String, num_cycles: u8, + op: String, + should_break: bool, } impl AssemblyOp { /// Returns [AssemblyOp] instantiated with the specified assembly instruction string and number /// of cycles it takes to execute the assembly instruction. - pub fn new(op: String, num_cycles: u8) -> Self { - Self { op, num_cycles } + pub fn new(context_name: String, num_cycles: u8, op: String, should_break: bool) -> Self { + Self { + context_name, + num_cycles, + op, + should_break, + } } - /// Returns the assembly instruction corresponding to this decorator. - pub fn op(&self) -> &String { - &self.op + /// Returns the context name for this operation. + pub fn context_name(&self) -> &str { + &self.context_name } /// Returns the number of VM cycles taken to execute the assembly instruction of this decorator. - pub fn num_cycles(&self) -> u8 { + pub const fn num_cycles(&self) -> u8 { self.num_cycles } + /// Returns the assembly instruction corresponding to this decorator. + pub fn op(&self) -> &str { + &self.op + } + + /// Returns `true` if there is a breakpoint for the current operation. + pub const fn should_break(&self) -> bool { + self.should_break + } + // STATE MUTATORS // -------------------------------------------------------------------------------------------- @@ -35,3 +53,13 @@ impl AssemblyOp { self.num_cycles = num_cycles; } } + +impl fmt::Display for AssemblyOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "context={}, operation={}, cost={}", + self.context_name, self.op, self.num_cycles, + ) + } +} diff --git a/miden/src/cli/debug/command.rs b/miden/src/cli/debug/command.rs index 70465a3687..71447f1f2a 100644 --- a/miden/src/cli/debug/command.rs +++ b/miden/src/cli/debug/command.rs @@ -3,8 +3,8 @@ pub enum DebugCommand { Continue, Next(usize), - RewindAll, - Rewind(usize), + Rewind, + Back(usize), PrintState, PrintStack, PrintStackItem(usize), @@ -36,7 +36,7 @@ impl DebugCommand { "n" | "next" => Self::parse_next(tokens.by_ref())?, "c" | "continue" => Self::Continue, "b" | "back" => Self::parse_back(tokens.by_ref())?, - "r" | "rewind" => Self::RewindAll, + "r" | "rewind" => Self::Rewind, "p" | "print" => Self::parse_print(tokens.by_ref())?, "l" | "clock" => Self::Clock, "h" | "?" | "help" => Self::Help, @@ -89,9 +89,9 @@ impl DebugCommand { n, err ) })?, - None => return Ok(Self::Rewind(1)), + None => return Ok(Self::Back(1)), }; - Ok(Self::Rewind(num_cycles)) + Ok(Self::Back(num_cycles)) } /// parse print command - p [m|s] [addr] diff --git a/miden/src/cli/debug/executor.rs b/miden/src/cli/debug/executor.rs index b081aff807..4aec1c2ad5 100644 --- a/miden/src/cli/debug/executor.rs +++ b/miden/src/cli/debug/executor.rs @@ -45,6 +45,9 @@ impl DebugExecutor { DebugCommand::Continue => { while let Some(new_vm_state) = self.next_vm_state() { self.vm_state = new_vm_state; + if self.should_break() { + break; + } } self.print_vm_state(); } @@ -53,23 +56,29 @@ impl DebugExecutor { match self.next_vm_state() { Some(next_vm_state) => { self.vm_state = next_vm_state; + if self.should_break() { + break; + } } None => break, } } self.print_vm_state(); } - DebugCommand::RewindAll => { - while let Some(new_vm_state) = self.prev_vm_state() { + DebugCommand::Rewind => { + while let Some(new_vm_state) = self.vm_state_iter.back() { self.vm_state = new_vm_state; } self.print_vm_state(); } - DebugCommand::Rewind(cycles) => { + DebugCommand::Back(cycles) => { for _cycle in 0..cycles { - match self.prev_vm_state() { + match self.vm_state_iter.back() { Some(new_vm_state) => { self.vm_state = new_vm_state; + if self.should_break() { + break; + } } None => break, } @@ -105,17 +114,6 @@ impl DebugExecutor { } } - /// iterates to the previous clock cycle. - fn prev_vm_state(&mut self) -> Option { - 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 // -------------------------------------------------------------------------------------------- @@ -213,4 +211,9 @@ impl DebugExecutor { println!("{}", message); } + + /// Returns `true` if the current state should break. + fn should_break(&self) -> bool { + self.vm_state.asmop.as_ref().map(|asm| asm.should_break()).unwrap_or(false) + } } diff --git a/miden/tests/integration/exec_iters.rs b/miden/tests/integration/exec_iters.rs index ba45b6c47f..ea1f2e8f1d 100644 --- a/miden/tests/integration/exec_iters.rs +++ b/miden/tests/integration/exec_iters.rs @@ -1,6 +1,6 @@ use super::build_debug_test; use processor::{AsmOpInfo, VmState}; -use vm_core::{utils::ToElements, Felt, FieldElement, Operation}; +use vm_core::{utils::ToElements, AssemblyOp, Felt, FieldElement, Operation}; // EXEC ITER TESTS // ================================================================= @@ -39,7 +39,10 @@ fn test_exec_iter() { clk: 2, ctx: 0, op: Some(Operation::Pad), - asmop: Some(AsmOpInfo::new("mem_storew.1".to_string(), 3, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 3, "mem_storew.1".to_string(), false), + 1, + )), stack: [0, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1].to_elements(), fmp, memory: Vec::new(), @@ -48,7 +51,10 @@ fn test_exec_iter() { clk: 3, ctx: 0, op: Some(Operation::Incr), - asmop: Some(AsmOpInfo::new("mem_storew.1".to_string(), 3, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 3, "mem_storew.1".to_string(), false), + 2, + )), stack: [1, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2].to_elements(), fmp, memory: Vec::new(), @@ -57,7 +63,10 @@ fn test_exec_iter() { clk: 4, ctx: 0, op: Some(Operation::MStoreW), - asmop: Some(AsmOpInfo::new("mem_storew.1".to_string(), 3, 3)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 3, "mem_storew.1".to_string(), false), + 3, + )), stack: [16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1].to_elements(), fmp, memory: mem.clone(), @@ -66,7 +75,10 @@ fn test_exec_iter() { clk: 5, ctx: 0, op: Some(Operation::Drop), - asmop: Some(AsmOpInfo::new("dropw".to_string(), 4, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 4, "dropw".to_string(), false), + 1, + )), stack: [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0].to_elements(), fmp, memory: mem.clone(), @@ -75,7 +87,10 @@ fn test_exec_iter() { clk: 6, ctx: 0, op: Some(Operation::Drop), - asmop: Some(AsmOpInfo::new("dropw".to_string(), 4, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 4, "dropw".to_string(), false), + 2, + )), stack: [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0].to_elements(), fmp, memory: mem.clone(), @@ -84,7 +99,10 @@ fn test_exec_iter() { clk: 7, ctx: 0, op: Some(Operation::Drop), - asmop: Some(AsmOpInfo::new("dropw".to_string(), 4, 3)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 4, "dropw".to_string(), false), + 3, + )), stack: [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0].to_elements(), fmp, memory: mem.clone(), @@ -93,7 +111,10 @@ fn test_exec_iter() { clk: 8, ctx: 0, op: Some(Operation::Drop), - asmop: Some(AsmOpInfo::new("dropw".to_string(), 4, 4)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 4, "dropw".to_string(), false), + 4, + )), stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0].to_elements(), fmp, memory: mem.clone(), @@ -102,7 +123,10 @@ fn test_exec_iter() { clk: 9, ctx: 0, op: Some(Operation::Push(Felt::new(17))), - asmop: Some(AsmOpInfo::new("push.17".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.17".to_string(), false), + 1, + )), stack: [17, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp, memory: mem.clone(), @@ -138,7 +162,10 @@ fn test_exec_iter() { clk: 13, ctx: 0, op: Some(Operation::Pad), - asmop: Some(AsmOpInfo::new("loc_store.0".to_string(), 4, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 4, "loc_store.0".to_string(), false), + 1, + )), stack: [0, 17, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0].to_elements(), fmp: next_fmp, memory: mem.clone(), @@ -147,7 +174,10 @@ fn test_exec_iter() { clk: 14, ctx: 0, op: Some(Operation::FmpAdd), - asmop: Some(AsmOpInfo::new("loc_store.0".to_string(), 4, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 4, "loc_store.0".to_string(), false), + 2, + )), stack: [2u64.pow(30) + 1, 17, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0] .to_elements(), fmp: next_fmp, @@ -157,7 +187,10 @@ fn test_exec_iter() { clk: 15, ctx: 0, op: Some(Operation::MStore), - asmop: Some(AsmOpInfo::new("loc_store.0".to_string(), 4, 3)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 4, "loc_store.0".to_string(), false), + 3, + )), stack: [17, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0].to_elements(), fmp: next_fmp, memory: vec![ @@ -169,7 +202,10 @@ fn test_exec_iter() { clk: 16, ctx: 0, op: Some(Operation::Drop), - asmop: Some(AsmOpInfo::new("loc_store.0".to_string(), 4, 4)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 4, "loc_store.0".to_string(), false), + 4, + )), stack: [12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0].to_elements(), fmp: next_fmp, memory: vec![ diff --git a/miden/tests/integration/operations/decorators/asmop.rs b/miden/tests/integration/operations/decorators/asmop.rs index 93e22fc37b..79659c888b 100644 --- a/miden/tests/integration/operations/decorators/asmop.rs +++ b/miden/tests/integration/operations/decorators/asmop.rs @@ -1,6 +1,6 @@ use crate::build_debug_test; use processor::{AsmOpInfo, VmStateIterator}; -use vm_core::{Felt, Operation}; +use vm_core::{AssemblyOp, Felt, Operation}; #[test] fn asmop_one_span_block_test() { @@ -20,22 +20,34 @@ fn asmop_one_span_block_test() { }, VmStatePartial { clk: 2, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 3, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 4, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 5, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { @@ -66,22 +78,34 @@ fn asmop_with_one_procedure() { }, VmStatePartial { clk: 2, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 3, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 4, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 5, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("foo".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { @@ -116,62 +140,98 @@ fn asmop_repeat_test() { }, VmStatePartial { clk: 2, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 3, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 4, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 5, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { clk: 6, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 7, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 8, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 9, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { clk: 10, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 11, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 12, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 13, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { @@ -231,7 +291,10 @@ fn asmop_conditional_execution_test() { }, VmStatePartial { clk: 3, - asmop: Some(AsmOpInfo::new("eq".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "eq".to_string(), false), + 1, + )), op: Some(Operation::Eq), }, VmStatePartial { @@ -251,22 +314,34 @@ fn asmop_conditional_execution_test() { }, VmStatePartial { clk: 7, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 1, + )), op: Some(Operation::Pad), }, VmStatePartial { clk: 8, - asmop: Some(AsmOpInfo::new("push.1".to_string(), 2, 2)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 2, "push.1".to_string(), false), + 2, + )), op: Some(Operation::Incr), }, VmStatePartial { clk: 9, - asmop: Some(AsmOpInfo::new("push.2".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.2".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(2))), }, VmStatePartial { clk: 10, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { @@ -309,7 +384,10 @@ fn asmop_conditional_execution_test() { }, VmStatePartial { clk: 3, - asmop: Some(AsmOpInfo::new("eq".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "eq".to_string(), false), + 1, + )), op: Some(Operation::Eq), }, VmStatePartial { @@ -329,17 +407,26 @@ fn asmop_conditional_execution_test() { }, VmStatePartial { clk: 7, - asmop: Some(AsmOpInfo::new("push.3".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.3".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(3))), }, VmStatePartial { clk: 8, - asmop: Some(AsmOpInfo::new("push.4".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "push.4".to_string(), false), + 1, + )), op: Some(Operation::Push(Felt::new(4))), }, VmStatePartial { clk: 9, - asmop: Some(AsmOpInfo::new("add".to_string(), 1, 1)), + asmop: Some(AsmOpInfo::new( + AssemblyOp::new("#main".to_string(), 1, "add".to_string(), false), + 1, + )), op: Some(Operation::Add), }, VmStatePartial { diff --git a/processor/src/debug.rs b/processor/src/debug.rs index a6475eb821..11c7d6541e 100644 --- a/processor/src/debug.rs +++ b/processor/src/debug.rs @@ -3,7 +3,10 @@ use crate::{ System, Vec, }; use core::fmt; -use vm_core::{utils::string::String, Operation, StackOutputs, Word}; +use vm_core::{ + utils::string::{String, ToString}, + AssemblyOp, Operation, StackOutputs, Word, +}; /// VmState holds a current process state information at a specific clock cycle. #[derive(Clone, Debug, Eq, PartialEq)] @@ -24,8 +27,17 @@ impl fmt::Display for VmState { self.memory.iter().map(|x| (x.0, word_to_ints(&x.1))).collect(); write!( f, - "clk={}, op={:?}, asmop={:?}, fmp={}, stack={stack:?}, memory={memory:?}", - self.clk, self.op, self.asmop, self.fmp + "clk={}{}{}, fmp={}, stack={stack:?}, memory={memory:?}", + self.clk, + match self.op { + Some(op) => format!(", op={op}"), + None => "".to_string(), + }, + match &self.asmop { + Some(op) => format!(", {op}"), + None => "".to_string(), + }, + self.fmp ) } } @@ -75,19 +87,21 @@ impl VmStateIterator { // keeps track of the next assembly op in the list. It's the same as the current asmop // when the current asmop is last in the list - let next_asmop = if self.asmop_idx < assembly_ops.len() { + let next_asmop = if self.forward && self.asmop_idx < assembly_ops.len() { &assembly_ops[self.asmop_idx] } else { - &assembly_ops[self.asmop_idx - 1] + &assembly_ops[self.asmop_idx.saturating_sub(1)] }; // keeps track of the current assembly op in the list. It's the same as the next asmop // when the clock cycle is less than the clock cycle of the first asmop. let (curr_asmop, cycle_idx) = if self.asmop_idx > 0 { + let a = self.clk; + let b = assembly_ops[self.asmop_idx - 1].0 as u32; ( &assembly_ops[self.asmop_idx - 1], // difference between current clock cycle and start clock cycle of the current asmop - (self.clk - assembly_ops[self.asmop_idx - 1].0 as u32) as u8, + (a.max(b) - a.min(b)) as u8, ) } else { (next_asmop, 0) //dummy value, never used. @@ -96,23 +110,18 @@ impl VmStateIterator { // if this is the first op in the sequence corresponding to the next asmop, returns a new // instance of [AsmOp] instantiated with next asmop, num_cycles and cycle_idx of 1. if next_asmop.0 as u32 == self.clk - 1 { - let asmop = Some(AsmOpInfo::new( - next_asmop.1.op().clone(), - next_asmop.1.num_cycles(), - 1, // cycle_idx starts at 1 instead of 0 to remove ambiguity - )); - (asmop, true) + // cycle_idx starts at 1 instead of 0 to remove ambiguity + let cycle_idx = 1; + let asmop = AsmOpInfo::new(next_asmop.1.clone(), cycle_idx); + (Some(asmop), true) } // if this is not the first asmop in the list and if this op is part of current asmop, // returns a new instance of [AsmOp] instantiated with current asmop, num_cycles and // cycle_idx of current op. else if self.asmop_idx > 0 && cycle_idx <= curr_asmop.1.num_cycles() { - let asmop = Some(AsmOpInfo::new( - curr_asmop.1.op().clone(), - curr_asmop.1.num_cycles(), - cycle_idx, /* diff between curr clock cycle and start clock cycle of the current asmop */ - )); - (asmop, false) + // diff between curr clock cycle and start clock cycle of the current asmop + let asmop = AsmOpInfo::new(curr_asmop.1.clone(), cycle_idx); + (Some(asmop), false) } // if the next asmop is the first in the list and is at a greater than current clock cycle // or if the current op is not a part of any asmop, return None. @@ -120,26 +129,16 @@ impl VmStateIterator { (None, false) } } -} - -impl Iterator for VmStateIterator { - type Item = Result; - fn next(&mut self) -> Option { - if self.clk > self.system.clk() { - match &self.error { - Some(_) => { - let error = core::mem::take(&mut self.error); - return Some(Err(error.unwrap())); - } - None => return None, - } + pub fn back(&mut self) -> Option { + if self.clk == 0 { + return None; } - // if we are changing iteration directions we must increment the clk counter - if !self.forward && self.clk < self.system.clk() { - self.clk += 1; - self.forward = true; + // if we are changing directions we must decrement the clk counter. + if self.forward { + self.clk = self.clk.saturating_sub(1); + self.forward = false; } let ctx = self.system.get_ctx_at(self.clk); @@ -152,10 +151,10 @@ impl Iterator for VmStateIterator { let (asmop, is_start) = self.get_asmop(); if is_start { - self.asmop_idx += 1; + self.asmop_idx -= 1; } - let result = Some(Ok(VmState { + let result = Some(VmState { clk: self.clk, ctx, op, @@ -163,26 +162,32 @@ impl Iterator for VmStateIterator { fmp: self.system.get_fmp_at(self.clk), stack: self.stack.get_state_at(self.clk), memory: self.chiplets.get_mem_state_at(ctx, self.clk), - })); + }); - self.clk += 1; + self.clk -= 1; result } } -impl DoubleEndedIterator for VmStateIterator { - fn next_back(&mut self) -> Option { - if self.clk == 0 { - return None; - } +impl Iterator for VmStateIterator { + type Item = Result; - self.clk -= 1; + fn next(&mut self) -> Option { + if self.clk > self.system.clk() { + match &self.error { + Some(_) => { + let error = core::mem::take(&mut self.error); + return Some(Err(error.unwrap())); + } + None => return None, + } + } - // if we are changing directions we must decrement the clk counter. - if self.forward && self.clk > 0 { - self.clk -= 1; - self.forward = false; + // if we are changing iteration directions we must increment the clk counter + if !self.forward && self.clk < self.system.clk() { + self.clk += 1; + self.forward = true; } let ctx = self.system.get_ctx_at(self.clk); @@ -195,10 +200,10 @@ impl DoubleEndedIterator for VmStateIterator { let (asmop, is_start) = self.get_asmop(); if is_start { - self.asmop_idx -= 1; + self.asmop_idx += 1; } - Some(Ok(VmState { + let result = Some(Ok(VmState { clk: self.clk, ctx, op, @@ -206,7 +211,11 @@ impl DoubleEndedIterator for VmStateIterator { fmp: self.system.get_fmp_at(self.clk), stack: self.stack.get_state_at(self.clk), memory: self.chiplets.get_mem_state_at(ctx, self.clk), - })) + })); + + self.clk += 1; + + result } } @@ -220,8 +229,7 @@ fn word_to_ints(word: &Word) -> [u64; 4] { /// AsmOp decorator. This index starts from 1 instead of 0. #[derive(Clone, Debug, Eq, PartialEq)] pub struct AsmOpInfo { - op: String, - num_cycles: u8, + asmop: AssemblyOp, cycle_idx: u8, } @@ -232,33 +240,34 @@ impl AsmOpInfo { /// Returns [AsmOpInfo] instantiated with the specified assembly instruction string, number of /// cycles it takes to execute the assembly instruction and op index in sequence of operations /// corresponding to the current assembly instruction. The first index is 1 instead of 0. - pub fn new(op: String, num_cycles: u8, cycle_idx: u8) -> Self { - Self { - op, - num_cycles, - cycle_idx, - } + pub fn new(asmop: AssemblyOp, cycle_idx: u8) -> Self { + Self { asmop, cycle_idx } + } + + /// Returns the context name for this operation. + pub fn context_name(&self) -> &str { + self.asmop.context_name() } /// Returns the assembly instruction corresponding to this state. - pub fn op(&self) -> &String { - &self.op + pub fn op(&self) -> &str { + self.asmop.op() } /// Returns the gerneralized form of assembly instruction corresponding to this state. pub fn op_generalized(&self) -> String { - let op_vec: Vec<&str> = self.op.split('.').collect(); + let op_vec: Vec<&str> = self.op().split('.').collect(); let keep_params = matches!(op_vec[0], "movdn" | "movup"); if !keep_params && op_vec.last().unwrap().parse::().is_ok() { op_vec.split_last().unwrap().1.join(".") } else { - self.op.clone() + self.op().to_string() } } /// Returns the number of VM cycles taken to execute the assembly instruction. pub fn num_cycles(&self) -> u8 { - self.num_cycles + self.asmop.num_cycles() } /// Returns the operation index of the operation at the specified clock cycle in the sequence @@ -266,4 +275,15 @@ impl AsmOpInfo { pub fn cycle_idx(&self) -> u8 { self.cycle_idx } + + /// Returns `true` if the debug should break for this line. + pub const fn should_break(&self) -> bool { + self.asmop.should_break() + } +} + +impl fmt::Display for AsmOpInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, cycles={}", self.asmop, self.cycle_idx) + } } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 21b95bb879..140feff136 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -12,7 +12,8 @@ pub use vm_core::{ }, errors::InputError, utils::DeserializationError, - Kernel, Operation, Program, ProgramInfo, QuadExtension, StackInputs, StackOutputs, Word, + AssemblyOp, Kernel, Operation, Program, ProgramInfo, QuadExtension, StackInputs, StackOutputs, + Word, }; use vm_core::{ code_blocks::{