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::{data_stack::DataStackVisitedItem, 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(|item| {
285                let DataStackVisitedItem::Value {
286                    type_hash,
287                    layout,
288                    data: bytes,
289                    range,
290                } = item else {
291                    return true;
292                };
293                assert_eq!(bytes.len(), layout.size());
294                if let Some((type_name, callback)) = self.printable.get(&type_hash) {
295                    println!(
296                        "- stack value #{} of type {}:\n{}",
297                        index,
298                        type_name,
299                        callback(self, bytes.as_ptr().cast::<()>())
300                    );
301                } else {
302                    println!(
303                        "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
304                    );
305                }
306                println!(
307                    "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
308                );
309                index += 1;
310                true
311            });
312        }
313        if self.registers {
314            println!("- registers position: {}", context.registers().position());
315            println!(
316                "- registers count: {}",
317                context.registers().registers_count()
318            );
319            println!("- registers barriers: {:?}", context.registers_barriers());
320        }
321        if self.registers_bytes {
322            println!("- registers bytes:\n{:?}", context.registers().as_bytes());
323        }
324        if self.visit_registers {
325            let mut index = 0;
326            let registers_count = context.registers().registers_count();
327            context.registers().visit(|item| {
328                let DataStackVisitedItem::Register {
329                    type_hash,
330                    layout,
331                    data: bytes,
332                    range,
333                    valid,
334                } = item
335                else {
336                    return true;
337                };
338                if let Some((type_name, callback)) = self.printable.get(&type_hash) {
339                    if valid {
340                        println!(
341                            "- register value #{} of type {}:\n{}",
342                            registers_count - index - 1,
343                            type_name,
344                            callback(self, bytes.as_ptr().cast::<()>())
345                        );
346                    } else {
347                        println!(
348                            "- invalid register value #{} of type {}",
349                            registers_count - index - 1,
350                            type_name
351                        );
352                    }
353                } else {
354                    println!(
355                        "- register value #{} of unknown type id {:?} and layout: {:?}",
356                        registers_count - index - 1,
357                        type_hash,
358                        layout
359                    );
360                }
361                println!(
362                    "- register value #{} bytes in range: {:?}:\n{:?}",
363                    registers_count - index - 1,
364                    range,
365                    bytes
366                );
367                index += 1;
368                true
369            });
370        }
371    }
372
373    fn try_halt(&self) {
374        if self.step_through {
375            print!("#{} | Confirm to step through...", self.step);
376            let _ = std::io::stdout().flush();
377            let mut command = String::new();
378            let _ = std::io::stdin().read_line(&mut command);
379        }
380    }
381}
382
383impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
384    fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
385        println!();
386        println!(
387            "* #{} PrintDebugger | Enter scope:\n{}",
388            self.step,
389            self.map(SourceMapLocation::symbol(scope.symbol()))
390        );
391        if self.mode.can_enter() {
392            self.print_extra(context);
393            self.try_halt();
394        }
395        println!();
396        self.step += 1;
397    }
398
399    fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
400        println!();
401        println!(
402            "* #{} PrintDebugger | Exit scope:\n{}",
403            self.step,
404            self.map(SourceMapLocation::symbol(scope.symbol()))
405        );
406        if self.mode.can_exit() {
407            self.print_extra(context);
408            self.try_halt();
409        }
410        println!();
411        self.step += 1;
412    }
413
414    fn on_enter_operation(
415        &mut self,
416        scope: &VmScope<SE>,
417        operation: &ScriptOperation<SE>,
418        position: usize,
419        context: &mut Context,
420        _: &Registry,
421    ) {
422        println!();
423        println!(
424            "* #{} PrintDebugger | Enter operation:\n{}",
425            self.step,
426            self.map(SourceMapLocation::symbol_operation(
427                scope.symbol(),
428                position
429            ))
430        );
431        if self.mode.can_enter() {
432            println!(
433                "- operation: {}",
434                if self.operation_details {
435                    format!("{operation:#?}")
436                } else {
437                    operation.label().to_owned()
438                }
439            );
440            self.print_extra(context);
441            self.try_halt();
442        }
443        println!();
444        self.step += 1;
445    }
446
447    fn on_exit_operation(
448        &mut self,
449        scope: &VmScope<SE>,
450        operation: &ScriptOperation<SE>,
451        position: usize,
452        context: &mut Context,
453        _: &Registry,
454    ) {
455        println!();
456        println!(
457            "* #{} PrintDebugger | Exit operation:\n{}",
458            self.step,
459            self.map(SourceMapLocation::symbol_operation(
460                scope.symbol(),
461                position
462            ))
463        );
464        if self.mode.can_exit() {
465            println!(
466                "- operation: {}",
467                if self.operation_details {
468                    format!("{operation:#?}")
469                } else {
470                    operation.label().to_owned()
471                }
472            );
473            self.print_extra(context);
474            self.try_halt();
475        }
476        println!();
477        self.step += 1;
478    }
479}