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