Skip to main content

torsh_jit/debugger/
mod.rs

1//! Interactive JIT debugger with breakpoints, stepping, and introspection
2//!
3//! This module provides comprehensive debugging capabilities for JIT compilation including:
4//! - Interactive debugging sessions with step-by-step execution
5//! - Breakpoint management and conditional breakpoints
6//! - State introspection and variable watching
7//! - Call stack analysis and navigation
8//! - Memory state inspection and analysis
9//! - Performance profiling and statistics
10//!
11//! # Architecture
12//!
13//! The debugger is organized into focused modules:
14//!
15//! - **core**: Core types, enums, and data structures
16//! - **breakpoints**: Breakpoint management system
17//! - **watch**: Watch expression management
18//! - **execution**: Debug execution engine with instrumentation
19//! - **state**: Call stack and memory state management
20//! - **interface**: User interface and command parsing
21//! - **session**: Debug session management and execution logic
22//!
23//! # Quick Start
24//!
25//! ```rust
26//! use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
27//! use torsh_jit::ComputationGraph;
28//!
29//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
30//! // Create a debugger with default configuration
31//! let mut debugger = JitDebugger::new(DebuggerConfig::default());
32//!
33//! // Debug a computation graph
34//! let graph = ComputationGraph::new();
35//! // let result = debugger.debug_graph(graph)?;
36//! // println!("Debug session completed with {} steps", result.execution_trace.len());
37//! # Ok(())
38//! # }
39//! ```
40//!
41//! # Interactive Debugging
42//!
43//! ```rust
44//! use torsh_jit::debugger::{JitDebugger, DebuggerConfig, BreakpointLocation};
45//! use torsh_jit::{ComputationGraph, NodeId};
46//!
47//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! let mut debugger = JitDebugger::new(DebuggerConfig::default());
49//!
50//! // Set breakpoints
51//! let bp_id = debugger.set_breakpoint(BreakpointLocation::GraphNode(NodeId::new(5)))?;
52//!
53//! // Add watch expressions
54//! let watch_id = debugger.add_watch("node_output".to_string())?;
55//!
56//! // Start debugging session (commented for doctest)
57//! let graph = ComputationGraph::new();
58//! // let result = debugger.debug_graph(graph)?;
59//! # Ok(())
60//! # }
61//! ```
62
63pub mod breakpoints;
64pub mod core;
65pub mod execution;
66pub mod interface;
67pub mod session;
68pub mod state;
69pub mod watch;
70
71// Re-export core types for convenience
72pub use core::{
73    Breakpoint, BreakpointId, BreakpointLocation, CallFrame, ContinueResult, DebugCommand,
74    DebugCommandResult, DebugSessionResult, DebugState, DebugStatistics, DebugValue,
75    DebuggerConfig, DisassemblyInstruction, DisassemblyView, EvaluationResult, ExecutionLocation,
76    ExecutionStep, InspectionResult, InspectionTarget, InstructionExecutionResult, MemoryView,
77    NodeExecutionResult, NodeMetadata, StepResult, TypeInfo, UiMode, Watch, WatchId, WatchUpdate,
78};
79
80// Re-export main components
81pub use breakpoints::BreakpointManager;
82pub use execution::{DebugExecutionEngine, OperationStatistics};
83pub use interface::DebuggerInterface;
84pub use session::DebugSession;
85pub use state::{CallStack, CallStackSummary, MemoryRegion, MemoryState, MemoryStats};
86pub use watch::{ExpressionEvaluator, WatchManager};
87
88use crate::{ir::IrModule, ComputationGraph, JitError, JitResult};
89use std::sync::{Arc, Mutex};
90
91/// Interactive JIT debugger
92///
93/// The main orchestrator that coordinates all debugging components to provide
94/// a comprehensive debugging experience for JIT-compiled code.
95///
96/// # Features
97///
98/// - **Step-by-step execution**: Execute code one instruction/node at a time
99/// - **Breakpoint management**: Set conditional and unconditional breakpoints
100/// - **Watch expressions**: Monitor variable and expression values
101/// - **State inspection**: Examine variables, memory, and call stack
102/// - **Performance profiling**: Track execution timing and statistics
103/// - **Interactive interface**: Command-line interface with rich formatting
104///
105/// # Examples
106///
107/// ## Basic Usage
108///
109/// ```rust
110/// use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
111/// use torsh_jit::ComputationGraph;
112///
113/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
114/// let mut debugger = JitDebugger::new(DebuggerConfig::default());
115/// let graph = ComputationGraph::new();
116/// // let result = debugger.debug_graph(graph)?;
117/// # Ok(())
118/// # }
119/// ```
120///
121/// ## Advanced Configuration
122///
123/// ```rust
124/// use torsh_jit::debugger::{JitDebugger, DebuggerConfig, UiMode};
125///
126/// let config = DebuggerConfig {
127///     enable_single_step: true,
128///     enable_breakpoints: true,
129///     enable_watches: true,
130///     enable_memory_view: true,
131///     enable_disassembly: true,
132///     max_trace_length: 10000,
133///     ui_mode: UiMode::Interactive,
134/// };
135///
136/// let mut debugger = JitDebugger::new(config);
137/// ```
138pub struct JitDebugger {
139    config: DebuggerConfig,
140    session: Option<Arc<Mutex<DebugSession>>>,
141    breakpoints: BreakpointManager,
142    watch_manager: WatchManager,
143    call_stack: CallStack,
144    execution_engine: DebugExecutionEngine,
145    ui_interface: DebuggerInterface,
146}
147
148impl JitDebugger {
149    /// Create a new JIT debugger
150    ///
151    /// # Arguments
152    /// * `config` - Configuration for the debugger
153    ///
154    /// # Examples
155    ///
156    /// ```rust
157    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
158    ///
159    /// let debugger = JitDebugger::new(DebuggerConfig::default());
160    /// ```
161    pub fn new(config: DebuggerConfig) -> Self {
162        Self {
163            breakpoints: BreakpointManager::new(),
164            watch_manager: WatchManager::new(),
165            call_stack: CallStack::new(),
166            execution_engine: DebugExecutionEngine::new(config.clone()),
167            ui_interface: DebuggerInterface::new(config.clone()),
168            session: None,
169            config,
170        }
171    }
172
173    /// Start a debugging session for a computation graph
174    ///
175    /// This method starts an interactive debugging session that allows step-by-step
176    /// execution through the computation graph with full debugging capabilities.
177    ///
178    /// # Arguments
179    /// * `graph` - The computation graph to debug
180    ///
181    /// # Returns
182    /// A `DebugSessionResult` containing the execution trace, final state,
183    /// command history, and statistics from the debugging session.
184    ///
185    /// # Examples
186    ///
187    /// ```rust
188    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
189    /// use torsh_jit::ComputationGraph;
190    ///
191    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
192    /// let mut debugger = JitDebugger::new(DebuggerConfig::default());
193    /// let graph = ComputationGraph::new();
194    /// // let result = debugger.debug_graph(graph)?;
195    /// // println!("Executed {} steps", result.execution_trace.len());
196    /// // println!("Final state: {:?}", result.final_state);
197    /// # Ok(())
198    /// # }
199    /// ```
200    pub fn debug_graph(&mut self, graph: ComputationGraph) -> JitResult<DebugSessionResult> {
201        let session = DebugSession::new(graph, self.config.clone());
202        let session_arc = Arc::new(Mutex::new(session));
203        self.session = Some(session_arc.clone());
204
205        // Start interactive debugging loop
206        self.interactive_debug_loop(session_arc)
207    }
208
209    /// Start a debugging session for an IR module
210    ///
211    /// This method starts an interactive debugging session for IR (Intermediate Representation)
212    /// code, allowing instruction-level debugging with full state inspection.
213    ///
214    /// # Arguments
215    /// * `ir_module` - The IR module to debug
216    ///
217    /// # Returns
218    /// A `DebugSessionResult` containing the execution trace, final state,
219    /// command history, and statistics from the debugging session.
220    ///
221    /// # Examples
222    ///
223    /// ```rust
224    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
225    /// use torsh_jit::ir::IrModule;
226    ///
227    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
228    /// let mut debugger = JitDebugger::new(DebuggerConfig::default());
229    /// let ir_module = IrModule::new("main".to_string());
230    /// // let result = debugger.debug_ir(ir_module)?;
231    /// # Ok(())
232    /// # }
233    /// ```
234    pub fn debug_ir(&mut self, ir_module: IrModule) -> JitResult<DebugSessionResult> {
235        let session = DebugSession::from_ir(ir_module, self.config.clone());
236        let session_arc = Arc::new(Mutex::new(session));
237        self.session = Some(session_arc.clone());
238
239        // Start interactive debugging loop
240        self.interactive_debug_loop(session_arc)
241    }
242
243    /// Interactive debugging loop
244    ///
245    /// The main loop that handles user interaction, command processing,
246    /// and execution control during a debugging session.
247    fn interactive_debug_loop(
248        &mut self,
249        session: Arc<Mutex<DebugSession>>,
250    ) -> JitResult<DebugSessionResult> {
251        let mut command_history = Vec::new();
252        let mut continue_execution = true;
253
254        self.ui_interface.show_welcome_message();
255
256        while continue_execution {
257            // Get current state
258            let current_state = {
259                let session_guard = session.lock().expect("lock should not be poisoned");
260                session_guard.get_current_state()
261            };
262
263            // Display current state
264            self.ui_interface.display_current_state(&current_state);
265
266            // Get user command
267            let command = self.ui_interface.get_user_command()?;
268            command_history.push(command.clone());
269
270            // Process command
271            match self.process_debug_command(command, session.clone())? {
272                DebugCommandResult::Continue => {}
273                DebugCommandResult::Exit => continue_execution = false,
274                DebugCommandResult::ExecutionComplete => {
275                    self.ui_interface.show_execution_complete();
276                    continue_execution = false;
277                }
278            }
279        }
280
281        // Generate session result
282        let session_guard = session.lock().expect("lock should not be poisoned");
283        Ok(DebugSessionResult {
284            execution_trace: session_guard.get_execution_trace(),
285            final_state: session_guard.get_current_state(),
286            command_history,
287            statistics: session_guard.get_statistics(),
288        })
289    }
290
291    /// Process a debug command
292    ///
293    /// Handles the execution of user commands during debugging sessions.
294    fn process_debug_command(
295        &mut self,
296        command: DebugCommand,
297        session: Arc<Mutex<DebugSession>>,
298    ) -> JitResult<DebugCommandResult> {
299        match command {
300            DebugCommand::Step => {
301                let mut session_guard = session.lock().expect("lock should not be poisoned");
302                session_guard.step()?;
303                Ok(DebugCommandResult::Continue)
304            }
305            DebugCommand::StepOver => {
306                let mut session_guard = session.lock().expect("lock should not be poisoned");
307                session_guard.step_over()?;
308                Ok(DebugCommandResult::Continue)
309            }
310            DebugCommand::StepInto => {
311                let mut session_guard = session.lock().expect("lock should not be poisoned");
312                session_guard.step_into()?;
313                Ok(DebugCommandResult::Continue)
314            }
315            DebugCommand::StepOut => {
316                let mut session_guard = session.lock().expect("lock should not be poisoned");
317                session_guard.step_out()?;
318                Ok(DebugCommandResult::Continue)
319            }
320            DebugCommand::Continue => {
321                let mut session_guard = session.lock().expect("lock should not be poisoned");
322                let result = session_guard.continue_execution()?;
323                match result {
324                    ContinueResult::Breakpoint => Ok(DebugCommandResult::Continue),
325                    ContinueResult::Completed => Ok(DebugCommandResult::ExecutionComplete),
326                }
327            }
328            DebugCommand::SetBreakpoint { location } => {
329                self.breakpoints.set_breakpoint(location.clone())?;
330                self.ui_interface.show_breakpoint_set(location);
331                Ok(DebugCommandResult::Continue)
332            }
333            DebugCommand::RemoveBreakpoint { id } => {
334                self.breakpoints.remove_breakpoint(id)?;
335                self.ui_interface.show_breakpoint_removed(id);
336                Ok(DebugCommandResult::Continue)
337            }
338            DebugCommand::ListBreakpoints => {
339                let breakpoints = self.breakpoints.list_breakpoints();
340                self.ui_interface.show_breakpoints(&breakpoints);
341                Ok(DebugCommandResult::Continue)
342            }
343            DebugCommand::Watch { expression } => {
344                let watch_id = self.watch_manager.add_watch(expression.clone())?;
345                self.ui_interface.show_watch_added(watch_id, &expression);
346                Ok(DebugCommandResult::Continue)
347            }
348            DebugCommand::Unwatch { id } => {
349                self.watch_manager.remove_watch(id)?;
350                self.ui_interface.show_watch_removed(id);
351                Ok(DebugCommandResult::Continue)
352            }
353            DebugCommand::ListWatches => {
354                let watches = self.watch_manager.list_watches();
355                self.ui_interface.show_watches(&watches);
356                Ok(DebugCommandResult::Continue)
357            }
358            DebugCommand::Inspect { target } => {
359                let session_guard = session.lock().expect("lock should not be poisoned");
360                let inspection_result = session_guard.inspect_target(&target)?;
361                self.ui_interface.show_inspection_result(&inspection_result);
362                Ok(DebugCommandResult::Continue)
363            }
364            DebugCommand::CallStack => {
365                let session_guard = session.lock().expect("lock should not be poisoned");
366                let call_stack = session_guard.get_call_stack();
367                self.ui_interface.show_call_stack(&call_stack);
368                Ok(DebugCommandResult::Continue)
369            }
370            DebugCommand::Locals => {
371                let session_guard = session.lock().expect("lock should not be poisoned");
372                let locals = session_guard.get_local_variables();
373                self.ui_interface.show_local_variables(&locals);
374                Ok(DebugCommandResult::Continue)
375            }
376            DebugCommand::Memory { address } => {
377                let session_guard = session.lock().expect("lock should not be poisoned");
378                let memory_view = session_guard.get_memory_view(address)?;
379                self.ui_interface.show_memory_view(&memory_view);
380                Ok(DebugCommandResult::Continue)
381            }
382            DebugCommand::Disassemble { location } => {
383                let session_guard = session.lock().expect("lock should not be poisoned");
384                let disassembly = session_guard.disassemble_at(location)?;
385                self.ui_interface.show_disassembly(&disassembly);
386                Ok(DebugCommandResult::Continue)
387            }
388            DebugCommand::Help => {
389                self.ui_interface.show_help();
390                Ok(DebugCommandResult::Continue)
391            }
392            DebugCommand::Quit => Ok(DebugCommandResult::Exit),
393        }
394    }
395
396    /// Set a breakpoint at a specific location
397    ///
398    /// # Arguments
399    /// * `location` - The location where the breakpoint should be set
400    ///
401    /// # Returns
402    /// The ID of the newly created breakpoint
403    ///
404    /// # Examples
405    ///
406    /// ```rust
407    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig, BreakpointLocation};
408    /// use torsh_jit::NodeId;
409    ///
410    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
411    /// let mut debugger = JitDebugger::new(DebuggerConfig::default());
412    /// let location = BreakpointLocation::GraphNode(NodeId::new(5));
413    /// let bp_id = debugger.set_breakpoint(location)?;
414    /// # Ok(())
415    /// # }
416    /// ```
417    pub fn set_breakpoint(&mut self, location: BreakpointLocation) -> JitResult<BreakpointId> {
418        self.breakpoints.set_breakpoint(location)
419    }
420
421    /// Remove a breakpoint
422    ///
423    /// # Arguments
424    /// * `id` - The ID of the breakpoint to remove
425    ///
426    /// # Examples
427    ///
428    /// ```rust
429    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig, BreakpointLocation};
430    /// use torsh_jit::NodeId;
431    ///
432    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
433    /// let mut debugger = JitDebugger::new(DebuggerConfig::default());
434    /// let location = BreakpointLocation::GraphNode(NodeId::new(0));
435    /// let bp_id = debugger.set_breakpoint(location)?;
436    /// debugger.remove_breakpoint(bp_id)?;
437    /// # Ok(())
438    /// # }
439    /// ```
440    pub fn remove_breakpoint(&mut self, id: BreakpointId) -> JitResult<()> {
441        self.breakpoints.remove_breakpoint(id)
442    }
443
444    /// Add a watch expression
445    ///
446    /// # Arguments
447    /// * `expression` - The expression to watch
448    ///
449    /// # Returns
450    /// The ID of the newly created watch
451    ///
452    /// # Examples
453    ///
454    /// ```rust
455    /// use torsh_jit::debugger::{JitDebugger, DebuggerConfig};
456    ///
457    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
458    /// let mut debugger = JitDebugger::new(DebuggerConfig::default());
459    /// let watch_id = debugger.add_watch("variable_name".to_string())?;
460    /// # Ok(())
461    /// # }
462    /// ```
463    pub fn add_watch(&mut self, expression: String) -> JitResult<WatchId> {
464        self.watch_manager.add_watch(expression)
465    }
466
467    /// Remove a watch expression
468    ///
469    /// # Arguments
470    /// * `id` - The ID of the watch to remove
471    pub fn remove_watch(&mut self, id: WatchId) -> JitResult<()> {
472        self.watch_manager.remove_watch(id)
473    }
474
475    /// Get current debugging state
476    ///
477    /// # Returns
478    /// The current debug state if a session is active, None otherwise
479    pub fn get_current_state(&self) -> Option<DebugState> {
480        if let Some(session) = &self.session {
481            let session_guard = session.lock().expect("lock should not be poisoned");
482            Some(session_guard.get_current_state())
483        } else {
484            None
485        }
486    }
487
488    /// Evaluate an expression in the current context
489    ///
490    /// # Arguments
491    /// * `expression` - The expression to evaluate
492    ///
493    /// # Returns
494    /// The evaluation result
495    pub fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
496        if let Some(session) = &self.session {
497            let session_guard = session.lock().expect("lock should not be poisoned");
498            session_guard.evaluate_expression(expression)
499        } else {
500            Err(JitError::RuntimeError(
501                "No active debug session".to_string(),
502            ))
503        }
504    }
505
506    /// Get breakpoint manager
507    pub fn breakpoints(&self) -> &BreakpointManager {
508        &self.breakpoints
509    }
510
511    /// Get mutable breakpoint manager
512    pub fn breakpoints_mut(&mut self) -> &mut BreakpointManager {
513        &mut self.breakpoints
514    }
515
516    /// Get watch manager
517    pub fn watch_manager(&self) -> &WatchManager {
518        &self.watch_manager
519    }
520
521    /// Get mutable watch manager
522    pub fn watch_manager_mut(&mut self) -> &mut WatchManager {
523        &mut self.watch_manager
524    }
525
526    /// Get debugger interface
527    pub fn interface(&self) -> &DebuggerInterface {
528        &self.ui_interface
529    }
530
531    /// Get mutable debugger interface
532    pub fn interface_mut(&mut self) -> &mut DebuggerInterface {
533        &mut self.ui_interface
534    }
535
536    /// Get execution engine
537    pub fn execution_engine(&self) -> &DebugExecutionEngine {
538        &self.execution_engine
539    }
540
541    /// Get mutable execution engine
542    pub fn execution_engine_mut(&mut self) -> &mut DebugExecutionEngine {
543        &mut self.execution_engine
544    }
545
546    /// Get configuration
547    pub fn config(&self) -> &DebuggerConfig {
548        &self.config
549    }
550
551    /// Update configuration
552    pub fn update_config(&mut self, config: DebuggerConfig) {
553        self.config = config.clone();
554        self.execution_engine.update_config(config.clone());
555        self.ui_interface.update_config(config);
556    }
557
558    /// Check if there is an active debug session
559    pub fn has_active_session(&self) -> bool {
560        self.session.is_some()
561    }
562
563    /// Clear the current debug session
564    pub fn clear_session(&mut self) {
565        self.session = None;
566    }
567
568    /// Get debug statistics from the current session
569    pub fn get_session_statistics(&self) -> Option<DebugStatistics> {
570        if let Some(session) = &self.session {
571            let session_guard = session.lock().expect("lock should not be poisoned");
572            Some(session_guard.get_statistics())
573        } else {
574            None
575        }
576    }
577}
578
579/// Convenience function to create a debugger with default configuration
580///
581/// # Examples
582///
583/// ```rust
584/// use torsh_jit::debugger::create_debugger;
585///
586/// let debugger = create_debugger();
587/// ```
588pub fn create_debugger() -> JitDebugger {
589    JitDebugger::new(DebuggerConfig::default())
590}
591
592/// Convenience function to create a debugger with custom configuration
593///
594/// # Arguments
595/// * `config` - Custom debugger configuration
596///
597/// # Examples
598///
599/// ```rust
600/// use torsh_jit::debugger::{create_debugger_with_config, DebuggerConfig, UiMode};
601///
602/// let config = DebuggerConfig {
603///     ui_mode: UiMode::Batch,
604///     max_trace_length: 5000,
605///     ..DebuggerConfig::default()
606/// };
607/// let debugger = create_debugger_with_config(config);
608/// ```
609pub fn create_debugger_with_config(config: DebuggerConfig) -> JitDebugger {
610    JitDebugger::new(config)
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[test]
618    fn test_debugger_creation() {
619        let debugger = create_debugger();
620        assert!(!debugger.has_active_session());
621        assert_eq!(debugger.config().max_trace_length, 10000);
622    }
623
624    #[test]
625    fn test_debugger_with_custom_config() {
626        let config = DebuggerConfig {
627            max_trace_length: 5000,
628            enable_memory_view: false,
629            ..DebuggerConfig::default()
630        };
631        let debugger = create_debugger_with_config(config);
632        assert_eq!(debugger.config().max_trace_length, 5000);
633        assert!(!debugger.config().enable_memory_view);
634    }
635
636    #[test]
637    fn test_breakpoint_management() {
638        let mut debugger = create_debugger();
639
640        let location = BreakpointLocation::GraphNode(crate::NodeId::new(0));
641        let bp_id = debugger.set_breakpoint(location).unwrap();
642
643        assert_eq!(debugger.breakpoints().count(), 1);
644        assert!(debugger.remove_breakpoint(bp_id).is_ok());
645        assert_eq!(debugger.breakpoints().count(), 0);
646    }
647
648    #[test]
649    fn test_watch_management() {
650        let mut debugger = create_debugger();
651
652        let watch_id = debugger.add_watch("test_expression".to_string()).unwrap();
653        assert_eq!(debugger.watch_manager().count(), 1);
654
655        assert!(debugger.remove_watch(watch_id).is_ok());
656        assert_eq!(debugger.watch_manager().count(), 0);
657    }
658
659    #[test]
660    fn test_configuration_update() {
661        let mut debugger = create_debugger();
662
663        let new_config = DebuggerConfig {
664            max_trace_length: 8000,
665            ..DebuggerConfig::default()
666        };
667
668        debugger.update_config(new_config.clone());
669        assert_eq!(debugger.config().max_trace_length, 8000);
670    }
671
672    #[test]
673    fn test_session_management() {
674        let debugger = create_debugger();
675
676        assert!(!debugger.has_active_session());
677        assert!(debugger.get_current_state().is_none());
678        assert!(debugger.get_session_statistics().is_none());
679    }
680
681    #[test]
682    fn test_expression_evaluation_without_session() {
683        let debugger = create_debugger();
684
685        let result = debugger.evaluate_expression("test");
686        assert!(result.is_err());
687    }
688}