Skip to main content

shape_vm/executor/
debugger_integration.rs

1//! Debugger integration for the VM
2//!
3//! This module handles debugger support, tracing, and debugging operations
4//! for the virtual machine.
5//!
6//! ## Wave 6.5 R-async-time migration (ADR-006 §2.7.4 / §2.7.7)
7//!
8//! Pre-bulldozer the display-side trait methods returned `ExternalValue`
9//! (a runtime-tier display carrier reading `ValueWord` tag bits). Both
10//! `ExternalValue` and `shape_value::nb_to_external` are deleted along
11//! with the rest of the dynamic-dispatch pipeline (CLAUDE.md "Forbidden
12//! Patterns"). The post-§2.7.4 display path is `KindedSlot::Debug` —
13//! `format!("{:?}", kinded_slot)` produces a runtime-only display string
14//! without a value-tier dependency. The trait surface here returns
15//! `Vec<String>` (debug-formatted) at every display-only site:
16//!
17//! - `stack_top` / `stack_values_vec` / `local_values_vec` use
18//!   `read_owned_kinded(idx)` per playbook §3 (WB2.4 retain-on-read for
19//!   the runtime carrier) and format the resulting `KindedSlot` via its
20//!   `Debug` impl. The temporary `KindedSlot` owns one share which its
21//!   own `Drop` releases when the formatted string is built.
22//!
23//! Module-binding inspection (`module_binding_values`,
24//! `set_module_binding`) reads through the §2.7.8 / Q10 parallel-kind
25//! track on `VirtualMachine.module_bindings` /
26//! `module_binding_kinds`. Both methods are live: the read returns
27//! each binding as a `(bits, NativeKind)` pair via
28//! `module_binding_read_kinded_raw`, and the write threads through
29//! `module_binding_write_kinded` (drop-prior + install-new with the
30//! same retain/release discipline `stack_write_kinded` enforces). No
31//! discriminator fabrication, no §2.7.7 #9 Bool-default fallback —
32//! the kind comes from the parallel track populated lockstep by every
33//! producer site.
34
35use crate::debugger::VMDebugger;
36
37/// Debugger integration for VirtualMachine.
38///
39/// Display-only methods (`stack_top`, `stack_values_vec`, `local_values_vec`)
40/// return debug-formatted strings (post-§2.7.4: `KindedSlot::Debug`).
41/// Data-flow methods (`module_binding_values`, `set_module_binding`) are
42/// §2.7.4-deferred pending the parallel-kind track for module bindings.
43pub trait DebuggerIntegration {
44    /// Trace VM state (for debugging)
45    fn trace_state(&self);
46
47    /// Trigger a debug break
48    fn debug_break(&self);
49
50    /// Get current instruction pointer
51    fn instruction_pointer(&self) -> usize;
52
53    /// Get stack size
54    fn stack_size(&self) -> usize;
55
56    /// Get top of stack as a debug-formatted string (display only).
57    fn stack_top(&self) -> Option<String>;
58
59    /// Get all stack values as debug-formatted strings (display only).
60    fn stack_values_vec(&self) -> Vec<String>;
61
62    /// Get call stack depth
63    fn call_stack_depth(&self) -> usize;
64
65    /// Get call frames (for debugging)
66    fn call_frames(&self) -> &[super::CallFrame];
67
68    /// Get local variables as debug-formatted strings (display only).
69    fn local_values_vec(&self) -> Vec<String>;
70
71    /// Get module-binding values for data-flow inspection.
72    ///
73    /// Returns each binding as a `(bits, NativeKind)` pair via the
74    /// §2.7.8 parallel-kind track on `VirtualMachine.module_bindings` /
75    /// `module_binding_kinds`. Read-side; no refcount change.
76    fn module_binding_values(&self) -> Vec<(u64, shape_value::NativeKind)>;
77
78    /// Set a module-binding variable by index.
79    ///
80    /// Threads the kinded write through `module_binding_write_kinded`,
81    /// which releases the prior occupant via `drop_with_kind` and
82    /// installs the new `(bits, kind)` pair — same retain/release
83    /// discipline `stack_write_kinded` enforces for stack slots
84    /// (ADR-006 §2.7.7 / §2.7.8). Caller transfers in one
85    /// strong-count share for heap-bearing kinds.
86    fn set_module_binding(&mut self, index: usize, bits: u64, kind: shape_value::NativeKind);
87
88    /// Set trace mode
89    fn set_trace_mode(&mut self, enabled: bool);
90
91    /// Get mutable reference to debugger
92    fn debugger_mut(&mut self) -> Option<&mut VMDebugger>;
93
94    /// Check if debugger is enabled
95    fn has_debugger(&self) -> bool;
96}
97
98impl DebuggerIntegration for super::VirtualMachine {
99    fn trace_state(&self) {
100        let stack_strs: Vec<String> = (0..self.sp)
101            .map(|i| {
102                // Borrow read — no refcount change, no temporary KindedSlot
103                // ownership transfer. Format the (bits, kind) pair directly.
104                let (bits, kind) = self.stack_read_kinded_raw(i);
105                format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
106            })
107            .collect();
108        println!("IP: {}, Stack: {:?}", self.ip, stack_strs);
109        if self.ip < self.program.instructions.len() {
110            println!("Next: {:?}", self.program.instructions[self.ip]);
111        }
112    }
113
114    fn debug_break(&self) {
115        println!("=== DEBUG BREAK ===");
116        println!("IP: {}, SP: {}", self.ip, self.sp);
117        let stack_strs: Vec<String> = (0..self.sp)
118            .map(|i| {
119                let (bits, kind) = self.stack_read_kinded_raw(i);
120                format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
121            })
122            .collect();
123        println!("Stack: {:?}", stack_strs);
124        if let Some(frame) = self.call_stack.last() {
125            let bp = frame.base_pointer;
126            let end = (bp + frame.locals_count).min(self.sp);
127            let locals: Vec<String> = (bp..end)
128                .map(|i| {
129                    let (bits, kind) = self.stack_read_kinded_raw(i);
130                    format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
131                })
132                .collect();
133            println!("Locals (bp={}): {:?}", bp, locals);
134        }
135        // ADR-006 §2.7.8 / Q10: walk both vecs lockstep via the
136        // kinded accessor, displaying `(bits, kind)` per slot.
137        let module_bindings_kinded: Vec<String> = (0..self.module_bindings_len())
138            .map(|i| {
139                let (bits, kind) = self.module_binding_read_kinded_raw(i);
140                format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
141            })
142            .collect();
143        println!("Globals: {:?}", module_bindings_kinded);
144        println!("Call stack depth: {}", self.call_stack.len());
145    }
146
147    // ===== Debugger Interface Methods =====
148
149    fn instruction_pointer(&self) -> usize {
150        self.ip
151    }
152
153    fn stack_size(&self) -> usize {
154        self.sp
155    }
156
157    fn stack_top(&self) -> Option<String> {
158        if self.sp > 0 {
159            let (bits, kind) = self.stack_read_kinded_raw(self.sp - 1);
160            Some(format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind))
161        } else {
162            None
163        }
164    }
165
166    fn stack_values_vec(&self) -> Vec<String> {
167        (0..self.sp)
168            .map(|i| {
169                let (bits, kind) = self.stack_read_kinded_raw(i);
170                format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
171            })
172            .collect()
173    }
174
175    fn call_stack_depth(&self) -> usize {
176        self.call_stack.len()
177    }
178
179    fn call_frames(&self) -> &[super::CallFrame] {
180        &self.call_stack
181    }
182
183    fn local_values_vec(&self) -> Vec<String> {
184        if let Some(frame) = self.call_stack.last() {
185            let bp = frame.base_pointer;
186            let end = (bp + frame.locals_count).min(self.sp);
187            (bp..end)
188                .map(|i| {
189                    let (bits, kind) = self.stack_read_kinded_raw(i);
190                    format!("{{ bits: {:#x}, kind: {:?} }}", bits, kind)
191                })
192                .collect()
193        } else {
194            vec![]
195        }
196    }
197
198    fn module_binding_values(&self) -> Vec<(u64, shape_value::NativeKind)> {
199        // ADR-006 §2.7.8 / Q10: module-binding storage now carries a
200        // parallel `NativeKind` track. The debugger walks both vecs in
201        // lockstep via the kinded accessor; no discriminator
202        // fabrication, no §2.7.7 #9 Bool-default fallback for live
203        // heap-bearing slots.
204        let len = self.module_bindings_len();
205        (0..len)
206            .map(|i| self.module_binding_read_kinded_raw(i))
207            .collect()
208    }
209
210    fn set_module_binding(&mut self, index: usize, bits: u64, kind: shape_value::NativeKind) {
211        // ADR-006 §2.7.8 / Q10: kinded write through the parallel
212        // track. `module_binding_write_kinded` releases the prior
213        // occupant via `drop_with_kind` and installs the new
214        // `(bits, kind)` pair atomically — same retain/release
215        // discipline `stack_write_kinded` enforces for stack slots.
216        // The caller is responsible for having retained the new
217        // share before this call (matching the §2.7.7 ownership
218        // contract for stack writes).
219        self.module_binding_write_kinded(index, bits, kind);
220    }
221
222    fn set_trace_mode(&mut self, enabled: bool) {
223        self.config.trace_execution = enabled;
224        if let Some(ref mut debugger) = self.debugger {
225            debugger.set_trace_mode(enabled);
226        }
227    }
228
229    fn debugger_mut(&mut self) -> Option<&mut VMDebugger> {
230        self.debugger.as_mut()
231    }
232
233    fn has_debugger(&self) -> bool {
234        self.debugger.is_some()
235    }
236}