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(¤t_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}