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}