Skip to content

Commit 80c0e21

Browse files
committed
Add MVP of virtual machine tracer
1 parent 2ebc979 commit 80c0e21

2 files changed

Lines changed: 173 additions & 36 deletions

File tree

core/engine/src/vm/mod.rs

Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ use boa_gc::{Finalize, Gc, Trace, custom_trace};
1818
use shadow_stack::ShadowStack;
1919
use std::{future::Future, ops::ControlFlow, pin::Pin, task};
2020

21+
#[cfg(feature = "trace")]
22+
pub use trace::{EmptyTracer, StdoutTracer, VirtualMachineTracer};
23+
2124
#[cfg(feature = "trace")]
2225
use crate::sys::time::Instant;
2326

@@ -53,6 +56,8 @@ pub(crate) mod opcode;
5356
pub(crate) mod shadow_stack;
5457
pub(crate) mod source_info;
5558

59+
mod trace;
60+
5661
#[cfg(feature = "flowgraph")]
5762
pub mod flowgraph;
5863

@@ -98,6 +103,10 @@ pub struct Vm {
98103

99104
#[cfg(feature = "trace")]
100105
pub(crate) trace: bool,
106+
107+
/// A tracer registered to emit VM events
108+
#[cfg(feature = "trace")]
109+
pub(crate) tracer: Box<dyn VirtualMachineTracer>,
101110
}
102111

103112
/// The stack holds the [`JsValue`]s for the calling convention and registers.
@@ -334,6 +343,7 @@ impl Vm {
334343
shadow_stack: ShadowStack::default(),
335344
#[cfg(feature = "trace")]
336345
trace: false,
346+
tracer: Box::new(EmptyTracer),
337347
}
338348
}
339349

@@ -581,40 +591,35 @@ impl Vm {
581591
}
582592
}
583593

584-
#[allow(clippy::print_stdout)]
585594
#[cfg(feature = "trace")]
586595
impl Context {
587-
const COLUMN_WIDTH: usize = 26;
588-
const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
589-
const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
590-
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
591-
const NUMBER_OF_COLUMNS: usize = 4;
596+
/// Sets the `Vm` tracer to the provided `VirtualMachineTracer` implementation
597+
pub fn set_virtual_machine_tracer(&mut self, tracer: Box<dyn VirtualMachineTracer>) {
598+
self.vm.tracer = tracer;
599+
}
592600

593601
pub(crate) fn trace_call_frame(&self) {
602+
use crate::vm::trace::{
603+
CallFrameMessage, CallFrameName, ExecutionStartMessage, VirtualMachineEvent,
604+
};
594605
let frame = self.vm.frame();
595-
let msg = if self.vm.frames.is_empty() {
596-
" VM Start ".to_string()
597-
} else {
598-
format!(
599-
" Call Frame -- {} ",
600-
frame.code_block().name().to_std_string_escaped()
601-
)
606+
let call_frame_message = CallFrameMessage {
607+
bytecode: frame.code_block.to_string(),
602608
};
609+
self.vm
610+
.tracer
611+
.emit_event(VirtualMachineEvent::CallFrameTrace(call_frame_message));
603612

604-
println!("{}", frame.code_block);
605-
println!(
606-
"{msg:-^width$}",
607-
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
608-
);
609-
println!(
610-
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
611-
"Time",
612-
"Opcode",
613-
"Operands",
614-
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
615-
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
616-
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
617-
);
613+
let call_frame_name = if self.vm.frames.is_empty() {
614+
CallFrameName::Global
615+
} else {
616+
CallFrameName::Name(frame.code_block().name().to_std_string_escaped())
617+
};
618+
self.vm
619+
.tracer
620+
.emit_event(VirtualMachineEvent::ExecutionStart(ExecutionStartMessage {
621+
call_frame_name,
622+
}));
618623
}
619624

620625
fn trace_execute_instruction<F>(
@@ -625,6 +630,8 @@ impl Context {
625630
where
626631
F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
627632
{
633+
use crate::vm::trace::{OpcodeExecutionMessage, VirtualMachineEvent};
634+
628635
let frame = self.vm.frame();
629636
let (instruction, _) = frame
630637
.code_block
@@ -647,7 +654,9 @@ impl Context {
647654
| Opcode::SuperCall
648655
| Opcode::SuperCallSpread
649656
| Opcode::SuperCallDerived => {
650-
println!();
657+
self.vm
658+
.tracer
659+
.emit_event(VirtualMachineEvent::ExecutionCallEvent);
651660
}
652661
_ => {}
653662
}
@@ -661,14 +670,16 @@ impl Context {
661670
.stack
662671
.display_trace(self.vm.frame(), self.vm.frames.len() - 1);
663672

664-
println!(
665-
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
666-
format!("{}μs", duration.as_micros()),
667-
format!("{}", opcode.as_str()),
668-
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
669-
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
670-
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
671-
);
673+
self.vm
674+
.tracer
675+
.emit_event(VirtualMachineEvent::ExecutionTrace(
676+
OpcodeExecutionMessage {
677+
opcode: opcode.as_str(),
678+
duration,
679+
operands,
680+
stack,
681+
},
682+
));
672683

673684
result
674685
}

core/engine/src/vm/trace.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use std::time::Duration;
2+
3+
/// The call frame name
4+
///
5+
/// This will have the name of the call frame provided or `Global` it's
6+
/// the global call frame.
7+
#[derive(Debug, Clone)]
8+
pub enum CallFrameName {
9+
Global,
10+
Name(String),
11+
}
12+
13+
/// A message that is emitted at the beginning of execution
14+
#[derive(Debug, Clone)]
15+
pub struct ExecutionStartMessage {
16+
pub call_frame_name: CallFrameName,
17+
}
18+
19+
/// A message that emits details about a call frame
20+
#[derive(Debug, Clone)]
21+
pub struct CallFrameMessage {
22+
pub bytecode: String,
23+
}
24+
25+
/// A message that emits instruction execution details about a call frame
26+
#[derive(Debug, Clone)]
27+
pub struct OpcodeExecutionMessage {
28+
pub opcode: &'static str,
29+
pub duration: Duration,
30+
pub operands: String,
31+
pub stack: String,
32+
}
33+
34+
/// The various events that are emitted from Boa's virtual machine.
35+
#[derive(Debug, Clone)]
36+
pub enum VirtualMachineEvent {
37+
/// This event is the first event triggered.
38+
///
39+
/// It emits information about the call frame.
40+
CallFrameTrace(CallFrameMessage),
41+
/// This event is triggered when the execution of a call frame is starting.
42+
ExecutionStart(ExecutionStartMessage),
43+
/// This event is triggered when executing an operation.
44+
///
45+
/// It provides information about the opcode execution
46+
ExecutionTrace(OpcodeExecutionMessage),
47+
/// This event is triggered when a opcode that calls is reached.
48+
///
49+
/// It signals that we about about to switch call frames.
50+
ExecutionCallEvent,
51+
}
52+
53+
/// A trait to define a tracer that plugs into Boa's `Vm`
54+
pub trait VirtualMachineTracer: std::fmt::Debug {
55+
/// Emits `VirtualMachineEvent`s from the virtual machine during execution
56+
fn emit_event(&self, _event: VirtualMachineEvent) {}
57+
}
58+
59+
/// A default empty virtual machine tracer that drops events submitted to it.
60+
#[derive(Debug, Clone, Copy)]
61+
pub struct EmptyTracer;
62+
63+
impl VirtualMachineTracer for EmptyTracer {}
64+
65+
/// `StdoutTracer` is a `VirtualMachineTracer` implementation that prints the events
66+
/// to stdout in a specific format.
67+
#[derive(Debug, Clone, Copy)]
68+
pub struct StdoutTracer;
69+
70+
impl StdoutTracer {
71+
const COLUMN_WIDTH: usize = 26;
72+
const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
73+
const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
74+
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
75+
const NUMBER_OF_COLUMNS: usize = 4;
76+
}
77+
78+
#[allow(clippy::print_stdout)]
79+
impl VirtualMachineTracer for StdoutTracer {
80+
fn emit_event(&self, event: VirtualMachineEvent) {
81+
match event {
82+
VirtualMachineEvent::ExecutionStart(start_message) => {
83+
let msg = match start_message.call_frame_name {
84+
CallFrameName::Global => " VM Start ".to_string(),
85+
CallFrameName::Name(name) => {
86+
format!(" Call Frame -- {name} ")
87+
}
88+
};
89+
90+
println!(
91+
"{msg:-^width$}",
92+
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
93+
);
94+
println!(
95+
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
96+
"Time",
97+
"Opcode",
98+
"Operands",
99+
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
100+
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
101+
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
102+
);
103+
}
104+
VirtualMachineEvent::ExecutionCallEvent => println!(),
105+
VirtualMachineEvent::CallFrameTrace(call_frame_message) => {
106+
println!("{}", call_frame_message.bytecode);
107+
}
108+
VirtualMachineEvent::ExecutionTrace(execution_messsage) => {
109+
let OpcodeExecutionMessage {
110+
opcode,
111+
duration,
112+
operands,
113+
stack,
114+
} = execution_messsage;
115+
116+
println!(
117+
"{:<TIME_COLUMN_WIDTH$} {opcode:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
118+
format!("{}μs", duration.as_micros()),
119+
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
120+
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
121+
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
122+
);
123+
}
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)