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}