intuicio_backend_vm/
debugger.rs

1use crate::scope::{VmScope, VmScopeSymbol};
2use intuicio_core::{
3    context::Context,
4    registry::Registry,
5    script::{ScriptExpression, ScriptOperation},
6};
7use intuicio_data::type_hash::TypeHash;
8use serde::{Deserialize, Serialize};
9use std::{
10    collections::HashMap,
11    io::Write,
12    sync::{Arc, RwLock},
13};
14
15pub type VmDebuggerHandle<SE> = Arc<RwLock<dyn VmDebugger<SE> + Send + Sync>>;
16pub type SourceMapHandle<UL> = Arc<RwLock<SourceMap<UL>>>;
17
18pub trait VmDebugger<SE: ScriptExpression> {
19    #[allow(unused_variables)]
20    fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
21
22    #[allow(unused_variables)]
23    fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
24
25    #[allow(unused_variables)]
26    fn on_enter_operation(
27        &mut self,
28        scope: &VmScope<SE>,
29        operation: &ScriptOperation<SE>,
30        position: usize,
31        context: &mut Context,
32        registry: &Registry,
33    ) {
34    }
35
36    #[allow(unused_variables)]
37    fn on_exit_operation(
38        &mut self,
39        scope: &VmScope<SE>,
40        operation: &ScriptOperation<SE>,
41        position: usize,
42        context: &mut Context,
43        registry: &Registry,
44    ) {
45    }
46
47    fn into_handle(self) -> VmDebuggerHandle<SE>
48    where
49        Self: Sized + Send + Sync + 'static,
50    {
51        Arc::new(RwLock::new(self))
52    }
53}
54
55#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub struct SourceMapLocation {
57    pub symbol: VmScopeSymbol,
58    pub operation: Option<usize>,
59}
60
61impl SourceMapLocation {
62    pub fn symbol(symbol: VmScopeSymbol) -> Self {
63        Self {
64            symbol,
65            operation: None,
66        }
67    }
68
69    pub fn symbol_operation(symbol: VmScopeSymbol, operation: usize) -> Self {
70        Self {
71            symbol,
72            operation: Some(operation),
73        }
74    }
75}
76
77#[derive(Debug, Default, Clone, Serialize, Deserialize)]
78pub struct SourceMap<UL> {
79    pub mappings: HashMap<SourceMapLocation, UL>,
80}
81
82impl<UL> SourceMap<UL> {
83    pub fn map(&self, location: SourceMapLocation) -> Option<&UL> {
84        self.mappings.get(&location)
85    }
86}
87
88#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
89pub enum PrintDebuggerMode {
90    Enter,
91    Exit,
92    #[default]
93    All,
94}
95
96impl PrintDebuggerMode {
97    pub fn can_enter(self) -> bool {
98        self == Self::All || self == Self::Enter
99    }
100
101    pub fn can_exit(self) -> bool {
102        self == Self::All || self == Self::Exit
103    }
104}
105
106#[derive(Default)]
107pub struct PrintDebugger {
108    pub source_map: SourceMap<String>,
109    pub stack: bool,
110    pub stack_bytes: bool,
111    pub visit_stack: bool,
112    pub registers: bool,
113    pub registers_bytes: bool,
114    pub visit_registers: bool,
115    pub operation_details: bool,
116    pub step_through: bool,
117    pub mode: PrintDebuggerMode,
118    #[allow(clippy::type_complexity)]
119    printable: HashMap<
120        TypeHash,
121        (
122            &'static str,
123            Box<dyn Fn(&Self, *const ()) -> String + Send + Sync>,
124        ),
125    >,
126    step: usize,
127}
128
129impl PrintDebugger {
130    pub fn full() -> Self {
131        Self {
132            source_map: Default::default(),
133            stack: true,
134            stack_bytes: true,
135            visit_stack: true,
136            registers: true,
137            registers_bytes: true,
138            visit_registers: true,
139            operation_details: true,
140            step_through: true,
141            mode: PrintDebuggerMode::All,
142            printable: Default::default(),
143            step: 0,
144        }
145    }
146
147    pub fn stack(mut self, mode: bool) -> Self {
148        self.stack = mode;
149        self
150    }
151
152    pub fn stack_bytes(mut self, mode: bool) -> Self {
153        self.stack_bytes = mode;
154        self
155    }
156
157    pub fn visit_stack(mut self, mode: bool) -> Self {
158        self.visit_stack = mode;
159        self
160    }
161
162    pub fn registers(mut self, mode: bool) -> Self {
163        self.registers = mode;
164        self
165    }
166
167    pub fn registers_bytes(mut self, mode: bool) -> Self {
168        self.registers_bytes = mode;
169        self
170    }
171
172    pub fn visit_registers(mut self, mode: bool) -> Self {
173        self.visit_registers = mode;
174        self
175    }
176
177    pub fn operation_details(mut self, mode: bool) -> Self {
178        self.operation_details = mode;
179        self
180    }
181
182    pub fn step_through(mut self, mode: bool) -> Self {
183        self.step_through = mode;
184        self
185    }
186
187    pub fn mode(mut self, mode: PrintDebuggerMode) -> Self {
188        self.mode = mode;
189        self
190    }
191
192    pub fn printable<T: std::fmt::Debug + 'static>(mut self) -> Self {
193        self.printable.insert(
194            TypeHash::of::<T>(),
195            (
196                std::any::type_name::<T>(),
197                Box::new(|_, pointer| unsafe {
198                    format!("{:#?}", pointer.cast::<T>().as_ref().unwrap())
199                }),
200            ),
201        );
202        self
203    }
204
205    pub fn printable_custom<T: 'static>(
206        mut self,
207        f: impl Fn(&Self, &T) -> String + Send + Sync + 'static,
208    ) -> Self {
209        self.printable.insert(
210            TypeHash::of::<T>(),
211            (
212                std::any::type_name::<T>(),
213                Box::new(move |debugger, pointer| unsafe {
214                    f(debugger, pointer.cast::<T>().as_ref().unwrap())
215                }),
216            ),
217        );
218        self
219    }
220
221    pub fn printable_raw<T: 'static>(
222        mut self,
223        f: impl Fn(&Self, *const ()) -> String + Send + Sync + 'static,
224    ) -> Self {
225        self.printable.insert(
226            TypeHash::of::<T>(),
227            (std::any::type_name::<T>(), Box::new(f)),
228        );
229        self
230    }
231
232    pub fn basic_printables(self) -> Self {
233        self.printable::<()>()
234            .printable::<bool>()
235            .printable::<i8>()
236            .printable::<i16>()
237            .printable::<i32>()
238            .printable::<i64>()
239            .printable::<i128>()
240            .printable::<isize>()
241            .printable::<u8>()
242            .printable::<u16>()
243            .printable::<u32>()
244            .printable::<u64>()
245            .printable::<u128>()
246            .printable::<usize>()
247            .printable::<f32>()
248            .printable::<f64>()
249            .printable::<char>()
250            .printable::<String>()
251    }
252
253    fn map(&self, location: SourceMapLocation) -> String {
254        self.source_map
255            .map(location)
256            .map(|mapping| mapping.to_owned())
257            .unwrap_or_else(|| format!("{location:?}"))
258    }
259
260    pub fn display<T>(&self, data: &T) -> Option<(&'static str, String)> {
261        let pointer = data as *const T as *const ();
262        self.display_raw(TypeHash::of::<T>(), pointer)
263    }
264
265    pub fn display_raw(
266        &self,
267        type_hash: TypeHash,
268        pointer: *const (),
269    ) -> Option<(&'static str, String)> {
270        let (type_name, callback) = self.printable.get(&type_hash)?;
271        let result = callback(self, pointer);
272        Some((type_name, result))
273    }
274
275    fn print_extra(&self, context: &mut Context) {
276        if self.stack {
277            println!("- stack position: {}", context.stack().position());
278        }
279        if self.stack_bytes {
280            println!("- stack bytes:\n{:?}", context.stack().as_bytes());
281        }
282        if self.visit_stack {
283            let mut index = 0;
284            context.stack().visit(|type_hash, layout, bytes, range, _| {
285                assert_eq!(bytes.len(), layout.size());
286                if let Some((type_name, callback)) = self.printable.get(&type_hash) {
287                    println!(
288                        "- stack value #{} of type {}:\n{}",
289                        index,
290                        type_name,
291                        callback(self, bytes.as_ptr().cast::<()>())
292                    );
293                } else {
294                    println!(
295                        "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
296                    );
297                }
298                println!(
299                    "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
300                );
301                index += 1;
302            });
303        }
304        if self.registers {
305            println!("- registers position: {}", context.registers().position());
306            println!(
307                "- registers count: {}",
308                context.registers().registers_count()
309            );
310            println!("- registers barriers: {:?}", context.registers_barriers());
311        }
312        if self.registers_bytes {
313            println!("- registers bytes:\n{:?}", context.registers().as_bytes());
314        }
315        if self.visit_registers {
316            let mut index = 0;
317            let registers_count = context.registers().registers_count();
318            context
319                .registers()
320                .visit(|type_hash, layout, bytes, range, valid| {
321                    if let Some((type_name, callback)) = self.printable.get(&type_hash) {
322                        if valid {
323                            println!(
324                                "- register value #{} of type {}:\n{}",
325                                registers_count - index - 1,
326                                type_name,
327                                callback(self, bytes.as_ptr().cast::<()>())
328                            );
329                        } else {
330                            println!(
331                                "- invalid register value #{} of type {}",
332                                registers_count - index - 1,
333                                type_name
334                            );
335                        }
336                    } else {
337                        println!(
338                            "- register value #{} of unknown type id {:?} and layout: {:?}",
339                            registers_count - index - 1,
340                            type_hash,
341                            layout
342                        );
343                    }
344                    println!(
345                        "- register value #{} bytes in range: {:?}:\n{:?}",
346                        registers_count - index - 1,
347                        range,
348                        bytes
349                    );
350                    index += 1;
351                });
352        }
353    }
354
355    fn try_halt(&self) {
356        if self.step_through {
357            print!("#{} | Confirm to step through...", self.step);
358            let _ = std::io::stdout().flush();
359            let mut command = String::new();
360            let _ = std::io::stdin().read_line(&mut command);
361        }
362    }
363}
364
365impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
366    fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
367        println!();
368        println!(
369            "* #{} PrintDebugger | Enter scope:\n{}",
370            self.step,
371            self.map(SourceMapLocation::symbol(scope.symbol()))
372        );
373        if self.mode.can_enter() {
374            self.print_extra(context);
375            self.try_halt();
376        }
377        println!();
378        self.step += 1;
379    }
380
381    fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
382        println!();
383        println!(
384            "* #{} PrintDebugger | Exit scope:\n{}",
385            self.step,
386            self.map(SourceMapLocation::symbol(scope.symbol()))
387        );
388        if self.mode.can_exit() {
389            self.print_extra(context);
390            self.try_halt();
391        }
392        println!();
393        self.step += 1;
394    }
395
396    fn on_enter_operation(
397        &mut self,
398        scope: &VmScope<SE>,
399        operation: &ScriptOperation<SE>,
400        position: usize,
401        context: &mut Context,
402        _: &Registry,
403    ) {
404        println!();
405        println!(
406            "* #{} PrintDebugger | Enter operation:\n{}",
407            self.step,
408            self.map(SourceMapLocation::symbol_operation(
409                scope.symbol(),
410                position
411            ))
412        );
413        if self.mode.can_enter() {
414            println!(
415                "- operation: {}",
416                if self.operation_details {
417                    format!("{operation:#?}")
418                } else {
419                    operation.label().to_owned()
420                }
421            );
422            self.print_extra(context);
423            self.try_halt();
424        }
425        println!();
426        self.step += 1;
427    }
428
429    fn on_exit_operation(
430        &mut self,
431        scope: &VmScope<SE>,
432        operation: &ScriptOperation<SE>,
433        position: usize,
434        context: &mut Context,
435        _: &Registry,
436    ) {
437        println!();
438        println!(
439            "* #{} PrintDebugger | Exit operation:\n{}",
440            self.step,
441            self.map(SourceMapLocation::symbol_operation(
442                scope.symbol(),
443                position
444            ))
445        );
446        if self.mode.can_exit() {
447            println!(
448                "- operation: {}",
449                if self.operation_details {
450                    format!("{operation:#?}")
451                } else {
452                    operation.label().to_owned()
453                }
454            );
455            self.print_extra(context);
456            self.try_halt();
457        }
458        println!();
459        self.step += 1;
460    }
461}