From 01e41cd03fa72e78cc7a00ec3bcf4e67e5ca357d Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Sat, 7 Mar 2026 12:19:32 -0600 Subject: [PATCH] Add MVP of virtual machine tracer --- core/engine/Cargo.toml | 3 + core/engine/src/vm/mod.rs | 87 ++++++++++++++----------- core/engine/src/vm/trace.rs | 126 ++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 core/engine/src/vm/trace.rs diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 50375155731..739fa26ddcd 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -67,6 +67,9 @@ flowgraph = [] # Enable Boa's VM instruction tracing. trace = ["js"] +# Enable Boa's VM instruction tracing printing to stdout +trace-stdout = ["trace"] + # Enable Boa's additional ECMAScript features for web browsers. annex-b = ["boa_ast/annex-b", "boa_parser/annex-b"] diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index cfeff08fb94..eb4faa5638d 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -18,6 +18,9 @@ use boa_gc::{Finalize, Gc, Trace, custom_trace}; use shadow_stack::ShadowStack; use std::{future::Future, ops::ControlFlow, pin::Pin, task}; +#[cfg(feature = "trace")] +pub use trace::{EmptyTracer, StdoutTracer, VirtualMachineTracer}; + #[cfg(feature = "trace")] use crate::sys::time::Instant; @@ -53,6 +56,9 @@ pub(crate) mod opcode; pub(crate) mod shadow_stack; pub(crate) mod source_info; +#[cfg(feature = "trace")] +mod trace; + #[cfg(feature = "flowgraph")] pub mod flowgraph; @@ -98,6 +104,10 @@ pub struct Vm { #[cfg(feature = "trace")] pub(crate) trace: bool, + + /// A tracer registered to emit VM events + #[cfg(feature = "trace")] + pub(crate) tracer: Box, } /// The stack holds the [`JsValue`]s for the calling convention and registers. @@ -334,6 +344,10 @@ impl Vm { shadow_stack: ShadowStack::default(), #[cfg(feature = "trace")] trace: false, + #[cfg(all(feature = "trace", not(feature = "trace-stdout")))] + tracer: Box::new(EmptyTracer), + #[cfg(feature = "trace-stdout")] + tracer: Box::new(StdoutTracer), } } @@ -581,40 +595,35 @@ impl Vm { } } -#[allow(clippy::print_stdout)] #[cfg(feature = "trace")] impl Context { - const COLUMN_WIDTH: usize = 26; - const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2; - const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH; - const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH; - const NUMBER_OF_COLUMNS: usize = 4; + /// Sets the `Vm` tracer to the provided `VirtualMachineTracer` implementation + pub fn set_virtual_machine_tracer(&mut self, tracer: Box) { + self.vm.tracer = tracer; + } pub(crate) fn trace_call_frame(&self) { + use crate::vm::trace::{ + CallFrameMessage, CallFrameName, ExecutionStartMessage, VirtualMachineEvent, + }; let frame = self.vm.frame(); - let msg = if self.vm.frames.is_empty() { - " VM Start ".to_string() - } else { - format!( - " Call Frame -- {} ", - frame.code_block().name().to_std_string_escaped() - ) + let call_frame_message = CallFrameMessage { + bytecode: frame.code_block.to_string(), }; + self.vm + .tracer + .emit_event(VirtualMachineEvent::CallFrameTrace(call_frame_message)); - println!("{}", frame.code_block); - println!( - "{msg:-^width$}", - width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 - ); - println!( - "{:( @@ -625,6 +634,8 @@ impl Context { where F: FnOnce(&mut Context, Opcode) -> ControlFlow, { + use crate::vm::trace::{OpcodeExecutionMessage, VirtualMachineEvent}; + let frame = self.vm.frame(); let (instruction, _) = frame .code_block @@ -647,7 +658,9 @@ impl Context { | Opcode::SuperCall | Opcode::SuperCallSpread | Opcode::SuperCallDerived => { - println!(); + self.vm + .tracer + .emit_event(VirtualMachineEvent::ExecutionCallEvent); } _ => {} } @@ -661,14 +674,16 @@ impl Context { .stack .display_trace(self.vm.frame(), self.vm.frames.len() - 1); - println!( - "{: { + let msg = match start_message.call_frame_name { + CallFrameName::Global => " VM Start ".to_string(), + CallFrameName::Name(name) => { + format!(" Call Frame -- {name} ") + } + }; + + println!( + "{msg:-^width$}", + width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 + ); + println!( + "{: println!(), + VirtualMachineEvent::CallFrameTrace(call_frame_message) => { + println!("{}", call_frame_message.bytecode); + } + VirtualMachineEvent::ExecutionTrace(execution_message) => { + let OpcodeExecutionMessage { + opcode, + duration, + operands, + stack, + } = execution_message; + + println!( + "{: