Skip to main content

torsh_jit/debugger/
state.rs

1//! State management for JIT debugging
2//!
3//! This module provides comprehensive state management capabilities including
4//! call stack management, memory state tracking, and execution state management.
5
6use super::core::{CallFrame, ExecutionLocation};
7use crate::{JitError, JitResult};
8use std::collections::HashMap;
9
10/// Call stack management
11#[derive(Debug, Clone)]
12pub struct CallStack {
13    frames: Vec<CallFrame>,
14    max_depth: usize,
15}
16
17impl CallStack {
18    /// Create a new call stack
19    pub fn new() -> Self {
20        Self {
21            frames: Vec::new(),
22            max_depth: 1000, // Default maximum depth to prevent stack overflow
23        }
24    }
25
26    /// Create a new call stack with specified maximum depth
27    ///
28    /// # Arguments
29    /// * `max_depth` - Maximum allowed call stack depth
30    pub fn with_max_depth(max_depth: usize) -> Self {
31        Self {
32            frames: Vec::new(),
33            max_depth,
34        }
35    }
36
37    /// Push a new frame onto the call stack
38    ///
39    /// # Arguments
40    /// * `frame` - The call frame to push
41    ///
42    /// # Returns
43    /// An error if the stack would exceed maximum depth
44    pub fn push(&mut self, frame: CallFrame) -> JitResult<()> {
45        if self.frames.len() >= self.max_depth {
46            return Err(JitError::RuntimeError(format!(
47                "Call stack overflow: maximum depth {} exceeded",
48                self.max_depth
49            )));
50        }
51        self.frames.push(frame);
52        Ok(())
53    }
54
55    /// Pop the top frame from the call stack
56    ///
57    /// # Returns
58    /// The return location from the popped frame, or Completed if stack is empty
59    pub fn pop(&mut self) -> ExecutionLocation {
60        if let Some(frame) = self.frames.pop() {
61            frame.return_location
62        } else {
63            ExecutionLocation::Completed
64        }
65    }
66
67    /// Get the current depth of the call stack
68    pub fn depth(&self) -> usize {
69        self.frames.len()
70    }
71
72    /// Check if the call stack is empty
73    pub fn is_empty(&self) -> bool {
74        self.frames.is_empty()
75    }
76
77    /// Get a reference to the current (top) frame
78    pub fn current_frame(&self) -> Option<&CallFrame> {
79        self.frames.last()
80    }
81
82    /// Get a mutable reference to the current (top) frame
83    pub fn current_frame_mut(&mut self) -> Option<&mut CallFrame> {
84        self.frames.last_mut()
85    }
86
87    /// Get a reference to a frame at a specific depth
88    ///
89    /// # Arguments
90    /// * `depth` - The depth (0 is the top frame)
91    pub fn frame_at(&self, depth: usize) -> Option<&CallFrame> {
92        if depth < self.frames.len() {
93            Some(&self.frames[self.frames.len() - 1 - depth])
94        } else {
95            None
96        }
97    }
98
99    /// Get all frames in the call stack (top to bottom)
100    pub fn frames(&self) -> &[CallFrame] {
101        &self.frames
102    }
103
104    /// Clear the entire call stack
105    pub fn clear(&mut self) {
106        self.frames.clear();
107    }
108
109    /// Find frames by function name
110    ///
111    /// # Arguments
112    /// * `function_name` - The name of the function to search for
113    ///
114    /// # Returns
115    /// A vector of indices where the function appears in the stack
116    pub fn find_frames_by_function(&self, function_name: &str) -> Vec<usize> {
117        let mut depths: Vec<usize> = self
118            .frames
119            .iter()
120            .enumerate()
121            .filter_map(|(i, frame)| {
122                if frame.function_name == function_name {
123                    Some(self.frames.len() - 1 - i) // Convert to depth from top
124                } else {
125                    None
126                }
127            })
128            .collect();
129        depths.sort(); // Sort depths from top (0) to bottom (highest)
130        depths
131    }
132
133    /// Get a summary of the call stack
134    pub fn get_summary(&self) -> CallStackSummary {
135        let function_calls: Vec<String> = self
136            .frames
137            .iter()
138            .rev() // Top to bottom
139            .map(|frame| frame.function_name.clone())
140            .collect();
141
142        CallStackSummary {
143            depth: self.frames.len(),
144            max_depth: self.max_depth,
145            function_calls,
146        }
147    }
148
149    /// Set the maximum depth for the call stack
150    pub fn set_max_depth(&mut self, max_depth: usize) {
151        self.max_depth = max_depth;
152    }
153
154    /// Get the maximum depth setting
155    pub fn max_depth(&self) -> usize {
156        self.max_depth
157    }
158}
159
160/// Call stack summary information
161#[derive(Debug, Clone)]
162pub struct CallStackSummary {
163    pub depth: usize,
164    pub max_depth: usize,
165    pub function_calls: Vec<String>,
166}
167
168/// Memory state management
169#[derive(Debug, Clone)]
170pub struct MemoryState {
171    memory: HashMap<u64, u8>,
172    allocated_regions: HashMap<u64, MemoryRegion>,
173    next_allocation_id: u64,
174    total_allocated: usize,
175    max_memory_usage: usize,
176}
177
178/// Information about an allocated memory region
179#[derive(Debug, Clone)]
180pub struct MemoryRegion {
181    pub id: u64,
182    pub start_address: u64,
183    pub size: usize,
184    pub allocation_time: std::time::SystemTime,
185    pub label: Option<String>,
186}
187
188impl MemoryState {
189    /// Create a new memory state
190    pub fn new() -> Self {
191        Self {
192            memory: HashMap::new(),
193            allocated_regions: HashMap::new(),
194            next_allocation_id: 1,
195            total_allocated: 0,
196            max_memory_usage: usize::MAX,
197        }
198    }
199
200    /// Create a new memory state with memory limit
201    ///
202    /// # Arguments
203    /// * `max_memory_usage` - Maximum allowed memory usage in bytes
204    pub fn with_limit(max_memory_usage: usize) -> Self {
205        Self {
206            memory: HashMap::new(),
207            allocated_regions: HashMap::new(),
208            next_allocation_id: 1,
209            total_allocated: 0,
210            max_memory_usage,
211        }
212    }
213
214    /// Read memory from a specific address
215    ///
216    /// # Arguments
217    /// * `address` - Starting address to read from
218    /// * `size` - Number of bytes to read
219    ///
220    /// # Returns
221    /// A vector containing the memory contents (uninitialized memory returns 0)
222    pub fn read_memory(&self, address: u64, size: usize) -> JitResult<Vec<u8>> {
223        let mut data = Vec::new();
224        for i in 0..size {
225            let byte = self.memory.get(&(address + i as u64)).copied().unwrap_or(0);
226            data.push(byte);
227        }
228        Ok(data)
229    }
230
231    /// Write memory to a specific address
232    ///
233    /// # Arguments
234    /// * `address` - Starting address to write to
235    /// * `data` - Data to write
236    pub fn write_memory(&mut self, address: u64, data: &[u8]) -> JitResult<()> {
237        for (i, &byte) in data.iter().enumerate() {
238            self.memory.insert(address + i as u64, byte);
239        }
240        Ok(())
241    }
242
243    /// Allocate a memory region
244    ///
245    /// # Arguments
246    /// * `address` - Starting address of the region
247    /// * `size` - Size of the region in bytes
248    /// * `label` - Optional label for the region
249    ///
250    /// # Returns
251    /// The allocation ID
252    pub fn allocate_region(
253        &mut self,
254        address: u64,
255        size: usize,
256        label: Option<String>,
257    ) -> JitResult<u64> {
258        // Check for memory limit
259        if self.total_allocated + size > self.max_memory_usage {
260            return Err(JitError::RuntimeError(format!(
261                "Memory allocation would exceed limit: {} + {} > {}",
262                self.total_allocated, size, self.max_memory_usage
263            )));
264        }
265
266        // Check for overlapping regions
267        for region in self.allocated_regions.values() {
268            let region_end = region.start_address + region.size as u64;
269            let new_end = address + size as u64;
270
271            if !(new_end <= region.start_address || address >= region_end) {
272                return Err(JitError::RuntimeError(format!(
273                    "Memory region overlap: new region [0x{:x}, 0x{:x}) overlaps with existing region [0x{:x}, 0x{:x})",
274                    address, new_end, region.start_address, region_end
275                )));
276            }
277        }
278
279        let allocation_id = self.next_allocation_id;
280        self.next_allocation_id += 1;
281
282        let region = MemoryRegion {
283            id: allocation_id,
284            start_address: address,
285            size,
286            allocation_time: std::time::SystemTime::now(),
287            label,
288        };
289
290        self.allocated_regions.insert(allocation_id, region);
291        self.total_allocated += size;
292
293        Ok(allocation_id)
294    }
295
296    /// Deallocate a memory region
297    ///
298    /// # Arguments
299    /// * `allocation_id` - The ID of the allocation to free
300    pub fn deallocate_region(&mut self, allocation_id: u64) -> JitResult<()> {
301        if let Some(region) = self.allocated_regions.remove(&allocation_id) {
302            self.total_allocated -= region.size;
303
304            // Clear the memory in this region
305            for i in 0..region.size {
306                self.memory.remove(&(region.start_address + i as u64));
307            }
308
309            Ok(())
310        } else {
311            Err(JitError::RuntimeError(format!(
312                "Allocation ID {} not found",
313                allocation_id
314            )))
315        }
316    }
317
318    /// Get information about all allocated regions
319    pub fn get_allocated_regions(&self) -> Vec<&MemoryRegion> {
320        self.allocated_regions.values().collect()
321    }
322
323    /// Find the region containing a specific address
324    ///
325    /// # Arguments
326    /// * `address` - The address to search for
327    ///
328    /// # Returns
329    /// Information about the region containing the address, if any
330    pub fn find_region_containing(&self, address: u64) -> Option<&MemoryRegion> {
331        self.allocated_regions.values().find(|region| {
332            address >= region.start_address && address < region.start_address + region.size as u64
333        })
334    }
335
336    /// Get memory usage statistics
337    pub fn get_memory_stats(&self) -> MemoryStats {
338        MemoryStats {
339            total_allocated: self.total_allocated,
340            max_memory_usage: self.max_memory_usage,
341            allocated_regions_count: self.allocated_regions.len(),
342            memory_utilization: if self.max_memory_usage > 0 {
343                self.total_allocated as f64 / self.max_memory_usage as f64
344            } else {
345                0.0
346            },
347        }
348    }
349
350    /// Clear all memory and allocations
351    pub fn clear(&mut self) {
352        self.memory.clear();
353        self.allocated_regions.clear();
354        self.total_allocated = 0;
355    }
356
357    /// Read a specific type from memory
358    ///
359    /// # Arguments
360    /// * `address` - Address to read from
361    ///
362    /// # Returns
363    /// The value read from memory
364    pub fn read_u32(&self, address: u64) -> JitResult<u32> {
365        let bytes = self.read_memory(address, 4)?;
366        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
367    }
368
369    /// Read a u64 from memory
370    pub fn read_u64(&self, address: u64) -> JitResult<u64> {
371        let bytes = self.read_memory(address, 8)?;
372        Ok(u64::from_le_bytes([
373            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
374        ]))
375    }
376
377    /// Read an f32 from memory
378    pub fn read_f32(&self, address: u64) -> JitResult<f32> {
379        let bytes = self.read_memory(address, 4)?;
380        Ok(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
381    }
382
383    /// Read an f64 from memory
384    pub fn read_f64(&self, address: u64) -> JitResult<f64> {
385        let bytes = self.read_memory(address, 8)?;
386        Ok(f64::from_le_bytes([
387            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
388        ]))
389    }
390
391    /// Write a u32 to memory
392    pub fn write_u32(&mut self, address: u64, value: u32) -> JitResult<()> {
393        let bytes = value.to_le_bytes();
394        self.write_memory(address, &bytes)
395    }
396
397    /// Write a u64 to memory
398    pub fn write_u64(&mut self, address: u64, value: u64) -> JitResult<()> {
399        let bytes = value.to_le_bytes();
400        self.write_memory(address, &bytes)
401    }
402
403    /// Write an f32 to memory
404    pub fn write_f32(&mut self, address: u64, value: f32) -> JitResult<()> {
405        let bytes = value.to_le_bytes();
406        self.write_memory(address, &bytes)
407    }
408
409    /// Write an f64 to memory
410    pub fn write_f64(&mut self, address: u64, value: f64) -> JitResult<()> {
411        let bytes = value.to_le_bytes();
412        self.write_memory(address, &bytes)
413    }
414
415    /// Set memory limit
416    pub fn set_memory_limit(&mut self, limit: usize) {
417        self.max_memory_usage = limit;
418    }
419
420    /// Get memory limit
421    pub fn memory_limit(&self) -> usize {
422        self.max_memory_usage
423    }
424}
425
426/// Memory usage statistics
427#[derive(Debug, Clone)]
428pub struct MemoryStats {
429    pub total_allocated: usize,
430    pub max_memory_usage: usize,
431    pub allocated_regions_count: usize,
432    pub memory_utilization: f64,
433}
434
435impl Default for CallStack {
436    fn default() -> Self {
437        Self::new()
438    }
439}
440
441impl Default for MemoryState {
442    fn default() -> Self {
443        Self::new()
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    #[test]
452    fn test_call_stack_operations() {
453        let mut stack = CallStack::new();
454        assert!(stack.is_empty());
455        assert_eq!(stack.depth(), 0);
456
457        let frame = CallFrame {
458            function_name: "test_function".to_string(),
459            location: ExecutionLocation::GraphNode(crate::NodeId::new(0)),
460            return_location: ExecutionLocation::GraphNode(crate::NodeId::new(1)),
461            local_variables: HashMap::new(),
462        };
463
464        stack.push(frame.clone()).expect("operation should succeed");
465        assert_eq!(stack.depth(), 1);
466        assert!(!stack.is_empty());
467
468        let current = stack
469            .current_frame()
470            .expect("current frame should be available");
471        assert_eq!(current.function_name, "test_function");
472
473        let return_location = stack.pop();
474        assert_eq!(stack.depth(), 0);
475        assert!(matches!(return_location, ExecutionLocation::GraphNode(_)));
476    }
477
478    #[test]
479    fn test_call_stack_max_depth() {
480        let mut stack = CallStack::with_max_depth(2);
481
482        let frame1 = CallFrame {
483            function_name: "func1".to_string(),
484            location: ExecutionLocation::GraphNode(crate::NodeId::new(0)),
485            return_location: ExecutionLocation::Completed,
486            local_variables: HashMap::new(),
487        };
488
489        let frame2 = CallFrame {
490            function_name: "func2".to_string(),
491            location: ExecutionLocation::GraphNode(crate::NodeId::new(1)),
492            return_location: ExecutionLocation::Completed,
493            local_variables: HashMap::new(),
494        };
495
496        let frame3 = CallFrame {
497            function_name: "func3".to_string(),
498            location: ExecutionLocation::GraphNode(crate::NodeId::new(2)),
499            return_location: ExecutionLocation::Completed,
500            local_variables: HashMap::new(),
501        };
502
503        assert!(stack.push(frame1).is_ok());
504        assert!(stack.push(frame2).is_ok());
505        assert!(stack.push(frame3).is_err()); // Should exceed max depth
506    }
507
508    #[test]
509    fn test_memory_state_basic_operations() {
510        let mut memory = MemoryState::new();
511
512        let data = vec![1, 2, 3, 4, 5];
513        memory
514            .write_memory(0x1000, &data)
515            .expect("memory write should succeed");
516
517        let read_data = memory
518            .read_memory(0x1000, 5)
519            .expect("memory read should succeed");
520        assert_eq!(read_data, data);
521
522        let partial_data = memory
523            .read_memory(0x1002, 2)
524            .expect("memory read should succeed");
525        assert_eq!(partial_data, vec![3, 4]);
526    }
527
528    #[test]
529    fn test_memory_allocation() {
530        let mut memory = MemoryState::new();
531
532        let id1 = memory
533            .allocate_region(0x1000, 100, Some("test_region".to_string()))
534            .expect("operation should succeed");
535        assert_eq!(memory.get_allocated_regions().len(), 1);
536
537        let region = memory
538            .find_region_containing(0x1050)
539            .expect("region containing address should be found");
540        assert_eq!(region.id, id1);
541        assert_eq!(region.size, 100);
542
543        memory
544            .deallocate_region(id1)
545            .expect("region deallocation should succeed");
546        assert_eq!(memory.get_allocated_regions().len(), 0);
547    }
548
549    #[test]
550    fn test_memory_overlap_detection() {
551        let mut memory = MemoryState::new();
552
553        memory
554            .allocate_region(0x1000, 100, None)
555            .expect("region allocation should succeed");
556
557        // This should fail due to overlap
558        let result = memory.allocate_region(0x1050, 100, None);
559        assert!(result.is_err());
560    }
561
562    #[test]
563    fn test_memory_typed_access() {
564        let mut memory = MemoryState::new();
565
566        memory
567            .write_u32(0x1000, 0x12345678)
568            .expect("u32 write should succeed");
569        memory
570            .write_f64(0x1004, 3.14159)
571            .expect("f64 write should succeed");
572
573        let u32_val = memory.read_u32(0x1000).expect("u32 read should succeed");
574        assert_eq!(u32_val, 0x12345678);
575
576        let f64_val = memory.read_f64(0x1004).expect("f64 read should succeed");
577        assert!((f64_val - 3.14159).abs() < 1e-10);
578    }
579
580    #[test]
581    fn test_memory_limit() {
582        let mut memory = MemoryState::with_limit(50);
583
584        // This should succeed
585        memory
586            .allocate_region(0x1000, 40, None)
587            .expect("region allocation should succeed");
588
589        // This should fail due to exceeding limit
590        let result = memory.allocate_region(0x2000, 20, None);
591        assert!(result.is_err());
592    }
593
594    #[test]
595    fn test_call_stack_find_functions() {
596        let mut stack = CallStack::new();
597
598        let frame1 = CallFrame {
599            function_name: "main".to_string(),
600            location: ExecutionLocation::GraphNode(crate::NodeId::new(0)),
601            return_location: ExecutionLocation::Completed,
602            local_variables: HashMap::new(),
603        };
604
605        let frame2 = CallFrame {
606            function_name: "helper".to_string(),
607            location: ExecutionLocation::GraphNode(crate::NodeId::new(1)),
608            return_location: ExecutionLocation::Completed,
609            local_variables: HashMap::new(),
610        };
611
612        let frame3 = CallFrame {
613            function_name: "main".to_string(),
614            location: ExecutionLocation::GraphNode(crate::NodeId::new(2)),
615            return_location: ExecutionLocation::Completed,
616            local_variables: HashMap::new(),
617        };
618
619        stack.push(frame1).expect("push operation should succeed");
620        stack.push(frame2).expect("push operation should succeed");
621        stack.push(frame3).expect("push operation should succeed");
622
623        let main_indices = stack.find_frames_by_function("main");
624        assert_eq!(main_indices.len(), 2);
625        assert_eq!(main_indices, vec![0, 2]); // depths from top
626
627        let helper_indices = stack.find_frames_by_function("helper");
628        assert_eq!(helper_indices.len(), 1);
629        assert_eq!(helper_indices[0], 1);
630    }
631
632    #[test]
633    fn test_memory_stats() {
634        let mut memory = MemoryState::with_limit(1000);
635
636        memory
637            .allocate_region(0x1000, 100, None)
638            .expect("region allocation should succeed");
639        memory
640            .allocate_region(0x2000, 200, None)
641            .expect("region allocation should succeed");
642
643        let stats = memory.get_memory_stats();
644        assert_eq!(stats.total_allocated, 300);
645        assert_eq!(stats.max_memory_usage, 1000);
646        assert_eq!(stats.allocated_regions_count, 2);
647        assert_eq!(stats.memory_utilization, 0.3);
648    }
649}