Skip to main content

torsh_jit/debugger/
session.rs

1//! Debug session management for JIT debugging
2//!
3//! This module provides the core debug session functionality including
4//! step-by-step execution, state management, and debugging operations.
5
6use super::core::{
7    ContinueResult, DebugState, DebugStatistics, DebugValue, DebuggerConfig,
8    DisassemblyInstruction, DisassemblyView, EvaluationResult, ExecutionLocation, ExecutionState,
9    ExecutionStep, InspectionResult, InspectionTarget, InstructionExecutionResult, MemoryView,
10    NodeExecutionResult, NodeMetadata, StepResult, TypeInfo,
11};
12use super::execution::DebugExecutionEngine;
13use super::state::{CallStack, MemoryState};
14use crate::{ir::IrModule, ComputationGraph, JitError, JitResult, NodeId};
15use std::collections::HashMap;
16use std::time::SystemTime;
17use torsh_core::{DType, Shape};
18
19/// Debug session managing the state of debugging
20pub struct DebugSession {
21    graph: Option<ComputationGraph>,
22    ir_module: Option<IrModule>,
23    current_location: ExecutionLocation,
24    execution_state: ExecutionState,
25    execution_trace: Vec<ExecutionStep>,
26    call_stack: CallStack,
27    variable_bindings: HashMap<String, DebugValue>,
28    memory_state: MemoryState,
29    statistics: DebugStatistics,
30    config: DebuggerConfig,
31    execution_engine: DebugExecutionEngine,
32}
33
34impl DebugSession {
35    /// Create a new debug session for a computation graph
36    ///
37    /// # Arguments
38    /// * `graph` - The computation graph to debug
39    /// * `config` - Configuration for the debug session
40    pub fn new(graph: ComputationGraph, config: DebuggerConfig) -> Self {
41        let initial_location = ExecutionLocation::GraphNode(
42            graph
43                .nodes()
44                .next()
45                .map(|(id, _)| id)
46                .unwrap_or(NodeId::new(0)),
47        );
48
49        let execution_engine = DebugExecutionEngine::new(config.clone());
50
51        Self {
52            graph: Some(graph),
53            ir_module: None,
54            current_location: initial_location,
55            execution_state: ExecutionState::new(),
56            execution_trace: Vec::new(),
57            call_stack: CallStack::new(),
58            variable_bindings: HashMap::new(),
59            memory_state: MemoryState::new(),
60            statistics: DebugStatistics::new(),
61            config,
62            execution_engine,
63        }
64    }
65
66    /// Create a new debug session for an IR module
67    ///
68    /// # Arguments
69    /// * `ir_module` - The IR module to debug
70    /// * `config` - Configuration for the debug session
71    pub fn from_ir(ir_module: IrModule, config: DebuggerConfig) -> Self {
72        let initial_location = ExecutionLocation::Instruction {
73            function: "main".to_string(),
74            instruction_index: 0,
75        };
76
77        let execution_engine = DebugExecutionEngine::new(config.clone());
78
79        Self {
80            graph: None,
81            ir_module: Some(ir_module),
82            current_location: initial_location,
83            execution_state: ExecutionState::new(),
84            execution_trace: Vec::new(),
85            call_stack: CallStack::new(),
86            variable_bindings: HashMap::new(),
87            memory_state: MemoryState::new(),
88            statistics: DebugStatistics::new(),
89            config,
90            execution_engine,
91        }
92    }
93
94    /// Execute a single step
95    ///
96    /// # Returns
97    /// The result of the step execution
98    pub fn step(&mut self) -> JitResult<StepResult> {
99        if self.is_execution_complete() {
100            return Err(JitError::AnalysisError(
101                "Execution already completed".to_string(),
102            ));
103        }
104
105        let step_start = std::time::Instant::now();
106
107        let result = match self.current_location.clone() {
108            ExecutionLocation::GraphNode(node_id) => self.step_graph_node(node_id),
109            ExecutionLocation::Instruction {
110                function,
111                instruction_index,
112            } => self.step_ir_instruction(&function, instruction_index),
113            ExecutionLocation::Completed => {
114                return Err(JitError::AnalysisError(
115                    "Execution already completed".to_string(),
116                ));
117            }
118        };
119
120        let step_duration = step_start.elapsed();
121        self.statistics.total_steps += 1;
122        self.statistics.total_execution_time += step_duration;
123
124        match result {
125            Ok(_) => {
126                if self.is_execution_complete() {
127                    Ok(StepResult::Completed)
128                } else {
129                    Ok(StepResult::Success)
130                }
131            }
132            Err(e) => Err(e),
133        }
134    }
135
136    /// Execute a step over (don't step into function calls)
137    ///
138    /// # Returns
139    /// The result of the step-over execution
140    pub fn step_over(&mut self) -> JitResult<StepResult> {
141        let current_call_depth = self.call_stack.depth();
142
143        loop {
144            let result = self.step()?;
145
146            match result {
147                StepResult::Completed => return Ok(StepResult::Completed),
148                StepResult::Success => {
149                    // Stop if we're at the same or shallower call depth
150                    if self.call_stack.depth() <= current_call_depth {
151                        break;
152                    }
153                }
154            }
155        }
156
157        Ok(StepResult::Success)
158    }
159
160    /// Execute a step into (step into function calls)
161    ///
162    /// # Returns
163    /// The result of the step-into execution
164    pub fn step_into(&mut self) -> JitResult<StepResult> {
165        // Step into is the same as regular step
166        self.step()
167    }
168
169    /// Execute a step out (continue until returning from current function)
170    ///
171    /// # Returns
172    /// The result of the step-out execution
173    pub fn step_out(&mut self) -> JitResult<StepResult> {
174        let target_depth = self.call_stack.depth().saturating_sub(1);
175
176        while self.call_stack.depth() > target_depth {
177            let result = self.step()?;
178            if matches!(result, StepResult::Completed) {
179                return Ok(StepResult::Completed);
180            }
181        }
182
183        Ok(StepResult::Success)
184    }
185
186    /// Continue execution until breakpoint or completion
187    ///
188    /// # Returns
189    /// The result of continue execution
190    pub fn continue_execution(&mut self) -> JitResult<ContinueResult> {
191        loop {
192            // Check for breakpoints at current location
193            if self.should_break_at_current_location() {
194                return Ok(ContinueResult::Breakpoint);
195            }
196
197            // Execute next step
198            match self.step() {
199                Ok(StepResult::Success) => {
200                    // Check if execution is complete
201                    if self.is_execution_complete() {
202                        return Ok(ContinueResult::Completed);
203                    }
204                }
205                Ok(StepResult::Completed) => {
206                    return Ok(ContinueResult::Completed);
207                }
208                Err(e) => return Err(e),
209            }
210        }
211    }
212
213    /// Step through a graph node
214    fn step_graph_node(&mut self, node_id: NodeId) -> JitResult<()> {
215        if let Some(graph) = &self.graph {
216            if let Some(node) = graph.node(node_id) {
217                // Record execution step
218                let step = ExecutionStep {
219                    location: self.current_location.clone(),
220                    timestamp: SystemTime::now(),
221                    operation: node.operation_type().to_string(),
222                    inputs: self.get_node_inputs(node_id),
223                    outputs: Vec::new(), // Will be filled after execution
224                    state_changes: HashMap::new(),
225                };
226
227                // Execute node operation using the execution engine
228                let result = self
229                    .execution_engine
230                    .execute_node_debug(node, graph, node_id)?;
231
232                // Update variable bindings
233                self.variable_bindings.insert(
234                    format!("node_{:?}", node_id),
235                    DebugValue::Tensor {
236                        data: result.data.clone(),
237                        shape: result.shape.clone(),
238                        dtype: result.dtype,
239                    },
240                );
241
242                // Move to next node
243                self.advance_to_next_graph_node(node_id)?;
244
245                // Complete the execution step record
246                let mut completed_step = step;
247                completed_step.outputs = vec![result];
248                self.execution_trace.push(completed_step);
249            }
250        }
251
252        Ok(())
253    }
254
255    /// Step through an IR instruction
256    fn step_ir_instruction(&mut self, function: &str, instruction_index: usize) -> JitResult<()> {
257        if let Some(ir_module) = self.ir_module.clone() {
258            if let Some(func) = ir_module.get_function(function) {
259                if let Some(instruction) = func.instructions().get(instruction_index) {
260                    // Record execution step
261                    let step = ExecutionStep {
262                        location: self.current_location.clone(),
263                        timestamp: SystemTime::now(),
264                        operation: format!("{:?}", instruction),
265                        inputs: self.get_instruction_inputs(instruction),
266                        outputs: Vec::new(),
267                        state_changes: HashMap::new(),
268                    };
269
270                    // Execute instruction using the execution engine
271                    let result = self.execution_engine.execute_instruction_debug(
272                        instruction,
273                        &ir_module,
274                        &mut self.execution_state,
275                    )?;
276
277                    // Update execution state
278                    self.update_execution_state_from_instruction(instruction, result)?;
279
280                    // Move to next instruction
281                    self.advance_to_next_instruction(function, instruction_index)?;
282
283                    // Complete execution step record
284                    self.execution_trace.push(step);
285                }
286            }
287        }
288
289        Ok(())
290    }
291
292    /// Get inputs for a node
293    fn get_node_inputs(&self, node_id: NodeId) -> Vec<NodeExecutionResult> {
294        // In a real implementation, this would get actual computed values from predecessor nodes
295        if let Some(graph) = &self.graph {
296            let inputs = graph.get_node_inputs(node_id);
297            inputs
298                .iter()
299                .map(|&input_id| {
300                    // Try to get the computed value from variable bindings
301                    let var_name = format!("node_{:?}", input_id);
302                    if let Some(DebugValue::Tensor { data, shape, dtype }) =
303                        self.variable_bindings.get(&var_name)
304                    {
305                        NodeExecutionResult {
306                            data: data.clone(),
307                            shape: shape.clone(),
308                            dtype: *dtype,
309                        }
310                    } else {
311                        // Default placeholder
312                        NodeExecutionResult {
313                            data: vec![1.0, 2.0, 3.0],
314                            shape: Shape::new(vec![3]),
315                            dtype: DType::F32,
316                        }
317                    }
318                })
319                .collect()
320        } else {
321            Vec::new()
322        }
323    }
324
325    /// Get inputs for an instruction
326    fn get_instruction_inputs(
327        &self,
328        instruction: &crate::ir::Instruction,
329    ) -> Vec<NodeExecutionResult> {
330        // Placeholder implementation - in practice would extract from instruction operands
331        Vec::new()
332    }
333
334    /// Advance to the next graph node
335    fn advance_to_next_graph_node(&mut self, current_node: NodeId) -> JitResult<()> {
336        if let Some(graph) = &self.graph {
337            // Find successors of current node
338            let successors: Vec<_> = graph
339                .edges()
340                .filter(|(source, _target, _edge)| *source == current_node)
341                .map(|(_source, target, _edge)| target)
342                .collect();
343
344            if let Some(next_node) = successors.first() {
345                self.current_location = ExecutionLocation::GraphNode(*next_node);
346            } else {
347                // No more nodes - execution complete
348                self.current_location = ExecutionLocation::Completed;
349            }
350        }
351        Ok(())
352    }
353
354    /// Advance to the next instruction
355    fn advance_to_next_instruction(
356        &mut self,
357        function: &str,
358        current_index: usize,
359    ) -> JitResult<()> {
360        if let Some(ir_module) = &self.ir_module {
361            if let Some(func) = ir_module.get_function(function) {
362                let next_index = current_index + 1;
363                if next_index < func.instructions().len() {
364                    self.current_location = ExecutionLocation::Instruction {
365                        function: function.to_string(),
366                        instruction_index: next_index,
367                    };
368                } else {
369                    // End of function
370                    if !self.call_stack.is_empty() {
371                        self.current_location = self.call_stack.pop();
372                    } else {
373                        self.current_location = ExecutionLocation::Completed;
374                    }
375                }
376            }
377        }
378        Ok(())
379    }
380
381    /// Update execution state from instruction result
382    fn update_execution_state_from_instruction(
383        &mut self,
384        instruction: &crate::ir::Instruction,
385        result: InstructionExecutionResult,
386    ) -> JitResult<()> {
387        match result {
388            InstructionExecutionResult::Value(value) => {
389                // Update variable bindings
390                let var_name = format!("temp_{}", self.execution_trace.len());
391                self.variable_bindings.insert(var_name, value);
392            }
393            InstructionExecutionResult::SideEffect => {
394                // Handle side effects (memory writes, etc.)
395                // This could update memory state or other side effects
396            }
397            InstructionExecutionResult::Return => {
398                // Handle function return
399                if !self.call_stack.is_empty() {
400                    let return_location = self.call_stack.pop();
401                    self.current_location = return_location;
402                } else {
403                    self.current_location = ExecutionLocation::Completed;
404                }
405            }
406            InstructionExecutionResult::NoOp => {
407                // No state change
408            }
409        }
410        Ok(())
411    }
412
413    /// Check if execution should break at the current location.
414    ///
415    /// `DebugSession` is a standalone execution context; it does not own a
416    /// `BreakpointManager`.  The `BreakpointManager` lives in the outer
417    /// `JitDebugger` wrapper, which intercepts `continue_execution` results and
418    /// checks registered breakpoints at the `JitDebugger` level before advancing
419    /// the session further.
420    ///
421    /// Within the session itself `continue_execution` drives the "run to
422    /// breakpoint" loop; since the session has no breakpoint registry it returns
423    /// `false` here and relies on the caller (`JitDebugger`) to inject the break
424    /// signal from the outside by not calling `continue_execution` again.
425    fn should_break_at_current_location(&self) -> bool {
426        // No BreakpointManager is available at DebugSession level.
427        // Breakpoint checking is handled by the JitDebugger wrapper.
428        false
429    }
430
431    /// Check if execution is complete
432    pub fn is_execution_complete(&self) -> bool {
433        matches!(self.current_location, ExecutionLocation::Completed)
434    }
435
436    /// Inspect a target (variable, memory location, etc.)
437    ///
438    /// # Arguments
439    /// * `target` - The target to inspect
440    ///
441    /// # Returns
442    /// The inspection result
443    pub fn inspect_target(&self, target: &InspectionTarget) -> JitResult<InspectionResult> {
444        match target {
445            InspectionTarget::Variable(name) => {
446                if let Some(value) = self.variable_bindings.get(name) {
447                    Ok(InspectionResult::Variable {
448                        name: name.clone(),
449                        value: value.clone(),
450                        type_info: self.get_type_info_for_value(value),
451                    })
452                } else {
453                    Err(JitError::RuntimeError(format!(
454                        "Variable '{}' not found",
455                        name
456                    )))
457                }
458            }
459            InspectionTarget::Node(node_id) => {
460                let var_name = format!("node_{:?}", node_id);
461                if let Some(value) = self.variable_bindings.get(&var_name) {
462                    Ok(InspectionResult::Node {
463                        node_id: *node_id,
464                        value: value.clone(),
465                        metadata: self.get_node_metadata(*node_id),
466                    })
467                } else {
468                    Err(JitError::RuntimeError(format!(
469                        "Node {:?} not executed yet",
470                        node_id
471                    )))
472                }
473            }
474            InspectionTarget::Memory(address) => {
475                let memory_content = self.memory_state.read_memory(*address, 16)?;
476                Ok(InspectionResult::Memory {
477                    address: *address,
478                    content: memory_content,
479                    size: 16,
480                })
481            }
482        }
483    }
484
485    /// Get type information for a debug value
486    fn get_type_info_for_value(&self, value: &DebugValue) -> TypeInfo {
487        match value {
488            DebugValue::Scalar(_) => TypeInfo {
489                type_name: "f64".to_string(),
490                size_bytes: 8,
491                alignment: 8,
492            },
493            DebugValue::Integer(_) => TypeInfo {
494                type_name: "i64".to_string(),
495                size_bytes: 8,
496                alignment: 8,
497            },
498            DebugValue::Boolean(_) => TypeInfo {
499                type_name: "bool".to_string(),
500                size_bytes: 1,
501                alignment: 1,
502            },
503            DebugValue::Tensor { dtype, shape, .. } => TypeInfo {
504                type_name: format!("Tensor<{:?}>", dtype),
505                size_bytes: shape.size(0).unwrap_or(1) * dtype.size_bytes(),
506                alignment: dtype.size_bytes(),
507            },
508        }
509    }
510
511    /// Get metadata for a node
512    fn get_node_metadata(&self, node_id: NodeId) -> NodeMetadata {
513        if let Some(graph) = &self.graph {
514            if let Some(node) = graph.node(node_id) {
515                let input_count = graph.get_node_inputs(node_id).len();
516                return NodeMetadata {
517                    operation: node.operation_type().to_string(),
518                    input_count,
519                    output_shape: node.output_shape.clone(),
520                    dtype: node.dtype,
521                };
522            }
523        }
524
525        NodeMetadata {
526            operation: "unknown".to_string(),
527            input_count: 0,
528            output_shape: Shape::new(vec![]),
529            dtype: DType::F32,
530        }
531    }
532
533    /// Evaluate an expression in the current context
534    ///
535    /// # Arguments
536    /// * `expression` - The expression to evaluate
537    ///
538    /// # Returns
539    /// The evaluation result
540    pub fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
541        // Simple expression evaluator
542        if let Some(value) = self.variable_bindings.get(expression) {
543            Ok(EvaluationResult {
544                expression: expression.to_string(),
545                result: value.clone(),
546                success: true,
547                error_message: None,
548            })
549        } else if expression.starts_with("node_") {
550            // Try to parse as node reference
551            if let Ok(node_index) = expression[5..].parse::<usize>() {
552                let var_name = format!("node_{}", node_index);
553                if let Some(value) = self.variable_bindings.get(&var_name) {
554                    Ok(EvaluationResult {
555                        expression: expression.to_string(),
556                        result: value.clone(),
557                        success: true,
558                        error_message: None,
559                    })
560                } else {
561                    Ok(EvaluationResult {
562                        expression: expression.to_string(),
563                        result: DebugValue::Scalar(0.0),
564                        success: false,
565                        error_message: Some("Node not executed yet".to_string()),
566                    })
567                }
568            } else {
569                Ok(EvaluationResult {
570                    expression: expression.to_string(),
571                    result: DebugValue::Scalar(0.0),
572                    success: false,
573                    error_message: Some("Invalid node reference".to_string()),
574                })
575            }
576        } else {
577            // Try to parse as literal value - try integer first, then float
578            if let Ok(value) = expression.parse::<i64>() {
579                Ok(EvaluationResult {
580                    expression: expression.to_string(),
581                    result: DebugValue::Integer(value),
582                    success: true,
583                    error_message: None,
584                })
585            } else if let Ok(value) = expression.parse::<f64>() {
586                Ok(EvaluationResult {
587                    expression: expression.to_string(),
588                    result: DebugValue::Scalar(value),
589                    success: true,
590                    error_message: None,
591                })
592            } else if let Ok(value) = expression.parse::<bool>() {
593                Ok(EvaluationResult {
594                    expression: expression.to_string(),
595                    result: DebugValue::Boolean(value),
596                    success: true,
597                    error_message: None,
598                })
599            } else {
600                Ok(EvaluationResult {
601                    expression: expression.to_string(),
602                    result: DebugValue::Scalar(0.0),
603                    success: false,
604                    error_message: Some("Expression not found or invalid".to_string()),
605                })
606            }
607        }
608    }
609
610    /// Get current debug state
611    pub fn get_current_state(&self) -> DebugState {
612        DebugState {
613            location: self.current_location.clone(),
614            call_stack: self.call_stack.clone(),
615            variables: self.variable_bindings.clone(),
616            execution_step: self.execution_trace.len(),
617            is_running: !self.is_execution_complete(),
618        }
619    }
620
621    /// Get execution trace
622    pub fn get_execution_trace(&self) -> Vec<ExecutionStep> {
623        self.execution_trace.clone()
624    }
625
626    /// Get call stack
627    pub fn get_call_stack(&self) -> CallStack {
628        self.call_stack.clone()
629    }
630
631    /// Get local variables
632    pub fn get_local_variables(&self) -> HashMap<String, DebugValue> {
633        // In a real implementation, this would return variables in the current scope
634        self.variable_bindings.clone()
635    }
636
637    /// Get memory view
638    pub fn get_memory_view(&self, address: u64) -> JitResult<MemoryView> {
639        let content = self.memory_state.read_memory(address, 64)?;
640        Ok(MemoryView {
641            start_address: address,
642            content,
643            size: 64,
644        })
645    }
646
647    /// Disassemble at location
648    pub fn disassemble_at(&self, location: ExecutionLocation) -> JitResult<DisassemblyView> {
649        match location {
650            ExecutionLocation::GraphNode(node_id) => {
651                if let Some(graph) = &self.graph {
652                    if let Some(node) = graph.node(node_id) {
653                        Ok(DisassemblyView {
654                            location,
655                            instructions: vec![DisassemblyInstruction {
656                                address: node_id.index() as u64,
657                                opcode: node.operation_type().to_string(),
658                                operands: format!(
659                                    "inputs: {}",
660                                    graph.get_node_inputs(node_id).len()
661                                ),
662                                comment: Some(format!("Output shape: {:?}", node.output_shape)),
663                            }],
664                        })
665                    } else {
666                        Err(JitError::RuntimeError("Node not found".to_string()))
667                    }
668                } else {
669                    Err(JitError::RuntimeError("No graph available".to_string()))
670                }
671            }
672            ExecutionLocation::Instruction {
673                ref function,
674                instruction_index,
675            } => {
676                if let Some(ir_module) = &self.ir_module {
677                    if let Some(func) = ir_module.get_function(function) {
678                        if let Some(instruction) = func.instructions().get(instruction_index) {
679                            Ok(DisassemblyView {
680                                location,
681                                instructions: vec![DisassemblyInstruction {
682                                    address: instruction_index as u64,
683                                    opcode: format!("{:?}", instruction.opcode),
684                                    operands: format!("operands: {}", instruction.operands.len()),
685                                    comment: Some(format!("result: {:?}", instruction.result)),
686                                }],
687                            })
688                        } else {
689                            Err(JitError::RuntimeError("Instruction not found".to_string()))
690                        }
691                    } else {
692                        Err(JitError::RuntimeError("Function not found".to_string()))
693                    }
694                } else {
695                    Err(JitError::RuntimeError("No IR module available".to_string()))
696                }
697            }
698            ExecutionLocation::Completed => {
699                Err(JitError::RuntimeError("Execution completed".to_string()))
700            }
701        }
702    }
703
704    /// Get debug statistics
705    pub fn get_statistics(&self) -> DebugStatistics {
706        // Combine session statistics with execution engine statistics
707        let mut stats = self.statistics.clone();
708        let engine_stats = self.execution_engine.get_statistics();
709
710        stats.total_steps = stats.total_steps.max(engine_stats.total_steps);
711        stats.total_execution_time = stats
712            .total_execution_time
713            .max(engine_stats.total_execution_time);
714
715        stats
716    }
717
718    /// Reset the debug session
719    pub fn reset(&mut self) {
720        self.execution_trace.clear();
721        self.call_stack.clear();
722        self.variable_bindings.clear();
723        self.memory_state.clear();
724        self.statistics = DebugStatistics::new();
725        self.execution_state = ExecutionState::new();
726
727        // Reset to initial location
728        if let Some(graph) = &self.graph {
729            self.current_location = ExecutionLocation::GraphNode(
730                graph
731                    .nodes()
732                    .next()
733                    .map(|(id, _)| id)
734                    .unwrap_or(NodeId::new(0)),
735            );
736        } else if self.ir_module.is_some() {
737            self.current_location = ExecutionLocation::Instruction {
738                function: "main".to_string(),
739                instruction_index: 0,
740            };
741        } else {
742            self.current_location = ExecutionLocation::Completed;
743        }
744    }
745
746    /// Get configuration
747    pub fn config(&self) -> &DebuggerConfig {
748        &self.config
749    }
750
751    /// Update configuration
752    pub fn update_config(&mut self, config: DebuggerConfig) {
753        self.config = config.clone();
754        self.execution_engine.update_config(config);
755    }
756
757    /// Set a variable value (for testing/debugging purposes)
758    pub fn set_variable(&mut self, name: String, value: DebugValue) {
759        self.variable_bindings.insert(name, value);
760    }
761
762    /// Get variable value
763    pub fn get_variable(&self, name: &str) -> Option<&DebugValue> {
764        self.variable_bindings.get(name)
765    }
766
767    /// Get execution engine statistics
768    pub fn get_execution_engine_statistics(
769        &self,
770    ) -> &std::collections::HashMap<String, super::execution::OperationStatistics> {
771        self.execution_engine.get_operation_statistics()
772    }
773
774    /// Get memory state
775    pub fn memory_state(&self) -> &MemoryState {
776        &self.memory_state
777    }
778
779    /// Get mutable memory state
780    pub fn memory_state_mut(&mut self) -> &mut MemoryState {
781        &mut self.memory_state
782    }
783}
784
785// Implement the ExpressionEvaluator trait for DebugSession
786impl super::watch::ExpressionEvaluator for DebugSession {
787    fn evaluate_expression(&self, expression: &str) -> JitResult<EvaluationResult> {
788        self.evaluate_expression(expression)
789    }
790}
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795
796    fn create_test_session() -> DebugSession {
797        let config = DebuggerConfig::default();
798        // Create a minimal graph for testing
799        let graph = ComputationGraph::new(); // Assuming this creates an empty graph
800        DebugSession::new(graph, config)
801    }
802
803    #[test]
804    fn test_session_creation() {
805        let session = create_test_session();
806        assert!(!session.is_execution_complete());
807        assert_eq!(session.execution_trace.len(), 0);
808        assert_eq!(session.variable_bindings.len(), 0);
809    }
810
811    #[test]
812    fn test_variable_management() {
813        let mut session = create_test_session();
814
815        let value = DebugValue::Scalar(42.0);
816        session.set_variable("test_var".to_string(), value.clone());
817
818        assert_eq!(session.get_variable("test_var"), Some(&value));
819        assert_eq!(session.get_variable("nonexistent"), None);
820    }
821
822    #[test]
823    fn test_expression_evaluation() {
824        let mut session = create_test_session();
825
826        // Test literal values
827        let result = session
828            .evaluate_expression("42.5")
829            .expect("expression evaluation should succeed");
830        assert!(result.success);
831        assert!(matches!(result.result, DebugValue::Scalar(42.5)));
832
833        let result = session
834            .evaluate_expression("123")
835            .expect("expression evaluation should succeed");
836        assert!(result.success);
837        assert!(matches!(result.result, DebugValue::Integer(123)));
838
839        let result = session
840            .evaluate_expression("true")
841            .expect("expression evaluation should succeed");
842        assert!(result.success);
843        assert!(matches!(result.result, DebugValue::Boolean(true)));
844
845        // Test variable lookup
846        session.set_variable("x".to_string(), DebugValue::Scalar(3.14));
847        let result = session
848            .evaluate_expression("x")
849            .expect("expression evaluation should succeed");
850        assert!(result.success);
851        assert!(matches!(result.result, DebugValue::Scalar(3.14)));
852
853        // Test unknown variable
854        let result = session
855            .evaluate_expression("unknown_var")
856            .expect("expression evaluation should succeed");
857        assert!(!result.success);
858        assert!(result.error_message.is_some());
859    }
860
861    #[test]
862    fn test_execution_state() {
863        let session = create_test_session();
864        let state = session.get_current_state();
865
866        assert!(state.is_running);
867        assert_eq!(state.execution_step, 0);
868        assert!(state.call_stack.is_empty());
869        assert!(state.variables.is_empty());
870    }
871
872    #[test]
873    fn test_memory_operations() {
874        let mut session = create_test_session();
875
876        let memory = session.memory_state_mut();
877        memory
878            .write_memory(0x1000, &[1, 2, 3, 4])
879            .expect("memory write should succeed");
880
881        let memory_view = session
882            .get_memory_view(0x1000)
883            .expect("memory view should be accessible");
884        assert_eq!(memory_view.start_address, 0x1000);
885        assert_eq!(&memory_view.content[0..4], &[1, 2, 3, 4]);
886    }
887
888    #[test]
889    fn test_session_reset() {
890        let mut session = create_test_session();
891
892        // Add some state
893        session.set_variable("test".to_string(), DebugValue::Scalar(1.0));
894        session.statistics.total_steps = 10;
895
896        // Reset and verify
897        session.reset();
898
899        assert_eq!(session.variable_bindings.len(), 0);
900        assert_eq!(session.execution_trace.len(), 0);
901        assert_eq!(session.statistics.total_steps, 0);
902        assert!(session.call_stack.is_empty());
903    }
904
905    #[test]
906    fn test_configuration_update() {
907        let mut session = create_test_session();
908
909        let mut new_config = DebuggerConfig::default();
910        new_config.max_trace_length = 5000;
911
912        session.update_config(new_config.clone());
913        assert_eq!(session.config().max_trace_length, 5000);
914    }
915
916    #[test]
917    fn test_inspection_targets() {
918        let mut session = create_test_session();
919
920        // Test variable inspection
921        session.set_variable("test_var".to_string(), DebugValue::Scalar(42.0));
922        let result = session.inspect_target(&InspectionTarget::Variable("test_var".to_string()));
923        assert!(result.is_ok());
924
925        // Test memory inspection
926        session
927            .memory_state_mut()
928            .write_memory(0x1000, &[1, 2, 3, 4])
929            .expect("operation should succeed");
930        let result = session.inspect_target(&InspectionTarget::Memory(0x1000));
931        assert!(result.is_ok());
932
933        // Test unknown variable
934        let result = session.inspect_target(&InspectionTarget::Variable("unknown".to_string()));
935        assert!(result.is_err());
936    }
937}