Skip to main content

intuicio_backend_vm/
debugger.rs

1//! Watching a script while it runs.
2//!
3//! A [`VmDebugger`] is called before and after every scope and every operation
4//! a [`VmScope`] runs. Attach one with
5//! [`VmScope::with_debugger`](crate::scope::VmScope::with_debugger), or hand it
6//! to the backend when a package is installed, and every scope of that script
7//! passes it down to its children.
8//!
9//! [`PrintDebugger`] is a ready-made one that prints what it sees, with the
10//! stack and the registers as far as it can read them. Give it a [`SourceMap`]
11//! and the printout names places in the original source instead of operation
12//! indices.
13use crate::scope::{VmScope, VmScopeSymbol};
14use intuicio_core::{
15    context::Context,
16    registry::Registry,
17    script::{ScriptExpression, ScriptOperation},
18};
19use intuicio_data::{data_stack::DataStackVisitedItem, type_hash::TypeHash};
20use serde::{Deserialize, Serialize};
21use std::{
22    collections::HashMap,
23    io::Write,
24    sync::{Arc, RwLock},
25};
26
27/// A shared debugger, as a scope holds it.
28///
29/// Every callback takes the lock with `try_write`, so a debugger that is busy
30/// elsewhere is skipped rather than waited for.
31pub type VmDebuggerHandle<SE> = Arc<RwLock<dyn VmDebugger<SE> + Send + Sync>>;
32
33/// A shared [`SourceMap`], for tools that build one while they run.
34pub type SourceMapHandle<UL> = Arc<RwLock<SourceMap<UL>>>;
35
36/// Callbacks a [`VmScope`] makes while it runs.
37///
38/// Every method does nothing by default, so implement only the ones you need.
39/// They run inside the step, with the context and the registry to hand, so a
40/// debugger can read and even change what the script is working on.
41pub trait VmDebugger<SE: ScriptExpression> {
42    /// Called once before the first operation of a scope.
43    #[allow(unused_variables)]
44    fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
45
46    /// Called when a step leaves the scope with nothing more to run, which is
47    /// after its last operation or when an operation ends it early.
48    #[allow(unused_variables)]
49    fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, registry: &Registry) {}
50
51    /// Called before each operation, with its index in the scope.
52    #[allow(unused_variables)]
53    fn on_enter_operation(
54        &mut self,
55        scope: &VmScope<SE>,
56        operation: &ScriptOperation<SE>,
57        position: usize,
58        context: &mut Context,
59        registry: &Registry,
60    ) {
61    }
62
63    /// Called after each operation, with the index it ran at.
64    #[allow(unused_variables)]
65    fn on_exit_operation(
66        &mut self,
67        scope: &VmScope<SE>,
68        operation: &ScriptOperation<SE>,
69        position: usize,
70        context: &mut Context,
71        registry: &Registry,
72    ) {
73    }
74
75    /// Wraps this debugger in a shared handle a scope can take.
76    fn into_handle(self) -> VmDebuggerHandle<SE>
77    where
78        Self: Sized + Send + Sync + 'static,
79    {
80        Arc::new(RwLock::new(self))
81    }
82}
83
84/// A place in a script, as a [`SourceMap`] keys it.
85#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub struct SourceMapLocation {
87    /// The script this location belongs to.
88    pub symbol: VmScopeSymbol,
89    /// Index of the operation, or [`None`] for the scope as a whole.
90    pub operation: Option<usize>,
91}
92
93impl SourceMapLocation {
94    /// A location naming a whole script.
95    pub fn symbol(symbol: VmScopeSymbol) -> Self {
96        Self {
97            symbol,
98            operation: None,
99        }
100    }
101
102    /// A location naming one operation of a script.
103    pub fn symbol_operation(symbol: VmScopeSymbol, operation: usize) -> Self {
104        Self {
105            symbol,
106            operation: Some(operation),
107        }
108    }
109}
110
111/// What each place in a script came from in the original source.
112///
113/// `UL` is whatever the frontend wants to point at: a line and column, a file
114/// name, a node id in a graph. A frontend fills this in while it produces the
115/// script, and a debugger reads it back.
116#[derive(Debug, Default, Clone, Serialize, Deserialize)]
117pub struct SourceMap<UL> {
118    /// Source location of each place in the script.
119    pub mappings: HashMap<SourceMapLocation, UL>,
120}
121
122impl<UL> SourceMap<UL> {
123    /// Returns what `location` came from, or [`None`] when it was never mapped.
124    pub fn map(&self, location: SourceMapLocation) -> Option<&UL> {
125        self.mappings.get(&location)
126    }
127}
128
129/// When a [`PrintDebugger`] prints the state around an operation or a scope.
130#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
131pub enum PrintDebuggerMode {
132    /// Print only on the way in.
133    Enter,
134    /// Print only on the way out.
135    Exit,
136    /// Print both times. The default.
137    #[default]
138    All,
139}
140
141impl PrintDebuggerMode {
142    /// Returns `true` when this mode prints on the way in.
143    pub fn can_enter(self) -> bool {
144        self == Self::All || self == Self::Enter
145    }
146
147    /// Returns `true` when this mode prints on the way out.
148    pub fn can_exit(self) -> bool {
149        self == Self::All || self == Self::Exit
150    }
151}
152
153/// A debugger that prints every step to standard output.
154///
155/// Everything is off by default, so a bare `PrintDebugger` only announces the
156/// scopes and operations it passes. Turn the parts you want on with the builder
157/// methods, or start from [`PrintDebugger::full`].
158///
159/// A value on the stack or in a register is only printed as a value when its
160/// type was registered with [`PrintDebugger::printable`] or one of its
161/// siblings. Anything else is printed as raw bytes.
162#[derive(Default)]
163pub struct PrintDebugger {
164    /// Names the printed places after the original source.
165    pub source_map: SourceMap<String>,
166    /// Print how many bytes the stack holds.
167    pub stack: bool,
168    /// Print the raw bytes of the stack.
169    pub stack_bytes: bool,
170    /// Print each value on the stack, one by one.
171    pub visit_stack: bool,
172    /// Print the register count and the scope barriers.
173    pub registers: bool,
174    /// Print the raw bytes of the register storage.
175    pub registers_bytes: bool,
176    /// Print each register, one by one.
177    pub visit_registers: bool,
178    /// Print the whole operation rather than only its name.
179    pub operation_details: bool,
180    /// Wait for a line on standard input after every printout.
181    pub step_through: bool,
182    /// When to print around an operation or a scope.
183    pub mode: PrintDebuggerMode,
184    #[allow(clippy::type_complexity)]
185    printable: HashMap<
186        TypeHash,
187        (
188            &'static str,
189            Box<dyn Fn(&Self, *const ()) -> String + Send + Sync>,
190        ),
191    >,
192    step: usize,
193}
194
195impl PrintDebugger {
196    /// A debugger with everything turned on, `step_through` included.
197    pub fn full() -> Self {
198        Self {
199            source_map: Default::default(),
200            stack: true,
201            stack_bytes: true,
202            visit_stack: true,
203            registers: true,
204            registers_bytes: true,
205            visit_registers: true,
206            operation_details: true,
207            step_through: true,
208            mode: PrintDebuggerMode::All,
209            printable: Default::default(),
210            step: 0,
211        }
212    }
213
214    /// Sets [`PrintDebugger::stack`], builder style.
215    pub fn stack(mut self, mode: bool) -> Self {
216        self.stack = mode;
217        self
218    }
219
220    /// Sets [`PrintDebugger::stack_bytes`], builder style.
221    pub fn stack_bytes(mut self, mode: bool) -> Self {
222        self.stack_bytes = mode;
223        self
224    }
225
226    /// Sets [`PrintDebugger::visit_stack`], builder style.
227    pub fn visit_stack(mut self, mode: bool) -> Self {
228        self.visit_stack = mode;
229        self
230    }
231
232    /// Sets [`PrintDebugger::registers`], builder style.
233    pub fn registers(mut self, mode: bool) -> Self {
234        self.registers = mode;
235        self
236    }
237
238    /// Sets [`PrintDebugger::registers_bytes`], builder style.
239    pub fn registers_bytes(mut self, mode: bool) -> Self {
240        self.registers_bytes = mode;
241        self
242    }
243
244    /// Sets [`PrintDebugger::visit_registers`], builder style.
245    pub fn visit_registers(mut self, mode: bool) -> Self {
246        self.visit_registers = mode;
247        self
248    }
249
250    /// Sets [`PrintDebugger::operation_details`], builder style.
251    pub fn operation_details(mut self, mode: bool) -> Self {
252        self.operation_details = mode;
253        self
254    }
255
256    /// Sets [`PrintDebugger::step_through`], builder style.
257    ///
258    /// With it on, every printout waits for a line on standard input, so the
259    /// script only moves when you press enter.
260    pub fn step_through(mut self, mode: bool) -> Self {
261        self.step_through = mode;
262        self
263    }
264
265    /// Sets [`PrintDebugger::mode`], builder style.
266    pub fn mode(mut self, mode: PrintDebuggerMode) -> Self {
267        self.mode = mode;
268        self
269    }
270
271    /// Prints values of type `T` with their [`Debug`](std::fmt::Debug) output.
272    pub fn printable<T: std::fmt::Debug + 'static>(mut self) -> Self {
273        self.printable.insert(
274            TypeHash::of::<T>(),
275            (
276                std::any::type_name::<T>(),
277                Box::new(|_, pointer| unsafe {
278                    format!("{:#?}", pointer.cast::<T>().as_ref().unwrap())
279                }),
280            ),
281        );
282        self
283    }
284
285    /// Prints values of type `T` with a function of your own.
286    pub fn printable_custom<T: 'static>(
287        mut self,
288        f: impl Fn(&Self, &T) -> String + Send + Sync + 'static,
289    ) -> Self {
290        self.printable.insert(
291            TypeHash::of::<T>(),
292            (
293                std::any::type_name::<T>(),
294                Box::new(move |debugger, pointer| unsafe {
295                    f(debugger, pointer.cast::<T>().as_ref().unwrap())
296                }),
297            ),
298        );
299        self
300    }
301
302    /// [`PrintDebugger::printable_custom`] with the value as a raw pointer.
303    ///
304    /// For a type that cannot be named as a Rust reference here. The pointer
305    /// given to `f` holds a value of `T`.
306    pub fn printable_raw<T: 'static>(
307        mut self,
308        f: impl Fn(&Self, *const ()) -> String + Send + Sync + 'static,
309    ) -> Self {
310        self.printable.insert(
311            TypeHash::of::<T>(),
312            (std::any::type_name::<T>(), Box::new(f)),
313        );
314        self
315    }
316
317    /// Registers the Rust primitives, [`char`] and [`String`] as printable.
318    pub fn basic_printables(self) -> Self {
319        self.printable::<()>()
320            .printable::<bool>()
321            .printable::<i8>()
322            .printable::<i16>()
323            .printable::<i32>()
324            .printable::<i64>()
325            .printable::<i128>()
326            .printable::<isize>()
327            .printable::<u8>()
328            .printable::<u16>()
329            .printable::<u32>()
330            .printable::<u64>()
331            .printable::<u128>()
332            .printable::<usize>()
333            .printable::<f32>()
334            .printable::<f64>()
335            .printable::<char>()
336            .printable::<String>()
337    }
338
339    fn map(&self, location: SourceMapLocation) -> String {
340        self.source_map
341            .map(location)
342            .map(|mapping| mapping.to_owned())
343            .unwrap_or_else(|| format!("{location:?}"))
344    }
345
346    /// Formats `data` with what was registered for its type.
347    ///
348    /// Returns the type name and the text, or [`None`] when the type was never
349    /// registered as printable.
350    pub fn display<T>(&self, data: &T) -> Option<(&'static str, String)> {
351        let pointer = data as *const T as *const ();
352        self.display_raw(TypeHash::of::<T>(), pointer)
353    }
354
355    /// [`PrintDebugger::display`] for a value whose type is only known at
356    /// runtime.
357    ///
358    /// `pointer` must hold a value of the type named by `type_hash`, which is
359    /// what the caller has to get right.
360    pub fn display_raw(
361        &self,
362        type_hash: TypeHash,
363        pointer: *const (),
364    ) -> Option<(&'static str, String)> {
365        let (type_name, callback) = self.printable.get(&type_hash)?;
366        let result = callback(self, pointer);
367        Some((type_name, result))
368    }
369
370    fn print_extra(&self, context: &mut Context) {
371        if self.stack {
372            println!("- stack position: {}", context.stack().position());
373        }
374        if self.stack_bytes {
375            println!("- stack bytes:\n{:?}", context.stack().as_bytes());
376        }
377        if self.visit_stack {
378            let mut index = 0;
379            context.stack().visit(|item| {
380                let DataStackVisitedItem::Value {
381                    type_hash,
382                    layout,
383                    data: bytes,
384                    range,
385                } = item else {
386                    return true;
387                };
388                assert_eq!(bytes.len(), layout.size());
389                if let Some((type_name, callback)) = self.printable.get(&type_hash) {
390                    println!(
391                        "- stack value #{} of type {}:\n{}",
392                        index,
393                        type_name,
394                        callback(self, bytes.as_ptr().cast::<()>())
395                    );
396                } else {
397                    println!(
398                        "- stack value #{index} of unknown type id {type_hash:?} and layout: {layout:?}"
399                    );
400                }
401                println!(
402                    "- stack value #{index} bytes in range {range:?}:\n{bytes:?}"
403                );
404                index += 1;
405                true
406            });
407        }
408        if self.registers {
409            println!("- registers position: {}", context.registers().position());
410            println!(
411                "- registers count: {}",
412                context.registers().registers_count()
413            );
414            println!("- registers barriers: {:?}", context.registers_barriers());
415        }
416        if self.registers_bytes {
417            println!("- registers bytes:\n{:?}", context.registers().as_bytes());
418        }
419        if self.visit_registers {
420            let mut index = 0;
421            let registers_count = context.registers().registers_count();
422            context.registers().visit(|item| {
423                let DataStackVisitedItem::Register {
424                    type_hash,
425                    layout,
426                    data: bytes,
427                    range,
428                    valid,
429                } = item
430                else {
431                    return true;
432                };
433                if let Some((type_name, callback)) = self.printable.get(&type_hash) {
434                    if valid {
435                        println!(
436                            "- register value #{} of type {}:\n{}",
437                            registers_count - index - 1,
438                            type_name,
439                            callback(self, bytes.as_ptr().cast::<()>())
440                        );
441                    } else {
442                        println!(
443                            "- invalid register value #{} of type {}",
444                            registers_count - index - 1,
445                            type_name
446                        );
447                    }
448                } else {
449                    println!(
450                        "- register value #{} of unknown type id {:?} and layout: {:?}",
451                        registers_count - index - 1,
452                        type_hash,
453                        layout
454                    );
455                }
456                println!(
457                    "- register value #{} bytes in range: {:?}:\n{:?}",
458                    registers_count - index - 1,
459                    range,
460                    bytes
461                );
462                index += 1;
463                true
464            });
465        }
466    }
467
468    fn try_halt(&self) {
469        if self.step_through {
470            print!("#{} | Confirm to step through...", self.step);
471            let _ = std::io::stdout().flush();
472            let mut command = String::new();
473            let _ = std::io::stdin().read_line(&mut command);
474        }
475    }
476}
477
478impl<SE: ScriptExpression + std::fmt::Debug> VmDebugger<SE> for PrintDebugger {
479    fn on_enter_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
480        println!();
481        println!(
482            "* #{} PrintDebugger | Enter scope:\n{}",
483            self.step,
484            self.map(SourceMapLocation::symbol(scope.symbol()))
485        );
486        if self.mode.can_enter() {
487            self.print_extra(context);
488            self.try_halt();
489        }
490        println!();
491        self.step += 1;
492    }
493
494    fn on_exit_scope(&mut self, scope: &VmScope<SE>, context: &mut Context, _: &Registry) {
495        println!();
496        println!(
497            "* #{} PrintDebugger | Exit scope:\n{}",
498            self.step,
499            self.map(SourceMapLocation::symbol(scope.symbol()))
500        );
501        if self.mode.can_exit() {
502            self.print_extra(context);
503            self.try_halt();
504        }
505        println!();
506        self.step += 1;
507    }
508
509    fn on_enter_operation(
510        &mut self,
511        scope: &VmScope<SE>,
512        operation: &ScriptOperation<SE>,
513        position: usize,
514        context: &mut Context,
515        _: &Registry,
516    ) {
517        println!();
518        println!(
519            "* #{} PrintDebugger | Enter operation:\n{}",
520            self.step,
521            self.map(SourceMapLocation::symbol_operation(
522                scope.symbol(),
523                position
524            ))
525        );
526        if self.mode.can_enter() {
527            println!(
528                "- operation: {}",
529                if self.operation_details {
530                    format!("{operation:#?}")
531                } else {
532                    operation.label().to_owned()
533                }
534            );
535            self.print_extra(context);
536            self.try_halt();
537        }
538        println!();
539        self.step += 1;
540    }
541
542    fn on_exit_operation(
543        &mut self,
544        scope: &VmScope<SE>,
545        operation: &ScriptOperation<SE>,
546        position: usize,
547        context: &mut Context,
548        _: &Registry,
549    ) {
550        println!();
551        println!(
552            "* #{} PrintDebugger | Exit operation:\n{}",
553            self.step,
554            self.map(SourceMapLocation::symbol_operation(
555                scope.symbol(),
556                position
557            ))
558        );
559        if self.mode.can_exit() {
560            println!(
561                "- operation: {}",
562                if self.operation_details {
563                    format!("{operation:#?}")
564                } else {
565                    operation.label().to_owned()
566                }
567            );
568            self.print_extra(context);
569            self.try_halt();
570        }
571        println!();
572        self.step += 1;
573    }
574}