From 0baa0093d3031092a1bca86a7a8e1c966d0fe7fd Mon Sep 17 00:00:00 2001 From: nekevss Date: Wed, 6 Dec 2023 19:49:19 -0500 Subject: [PATCH 01/11] Squash wasm-debugger to one commit --- Cargo.lock | 1 + cli/src/debug/function.rs | 4 +- cli/src/main.rs | 4 +- core/engine/src/bytecompiler/mod.rs | 5 + core/engine/src/context/mod.rs | 24 ++- core/engine/src/vm/code_block.rs | 43 ++++- core/engine/src/vm/mod.rs | 74 +++----- core/engine/src/vm/trace.rs | 285 ++++++++++++++++++++++++++++ ffi/wasm/Cargo.toml | 3 +- ffi/wasm/src/lib.rs | 160 +++++++++++++++- 10 files changed, 541 insertions(+), 62 deletions(-) create mode 100644 core/engine/src/vm/trace.rs diff --git a/Cargo.lock b/Cargo.lock index 29d0f173718..a8fe8228706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -601,6 +601,7 @@ dependencies = [ "boa_engine", "console_error_panic_hook", "getrandom", + "js-sys", "wasm-bindgen", "wasm-bindgen-test", ] diff --git a/cli/src/debug/function.rs b/cli/src/debug/function.rs index 8337ba708b7..96e2a7c6f5f 100644 --- a/cli/src/debug/function.rs +++ b/cli/src/debug/function.rs @@ -145,6 +145,7 @@ fn trace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult JsResult JsResult { +fn traceable(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let value = args.get_or_undefined(0); let traceable = args.get_or_undefined(1).to_boolean(); @@ -162,6 +163,7 @@ fn traceable(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult Result<(), io::Error> { add_runtime(&mut context); // Trace Output - context.set_trace(args.trace); + if args.trace { + context.init_trace(); + } if args.debug_object { init_boa_debug_object(&mut context); diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 13b8867882f..0740b5dae76 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -42,6 +42,9 @@ use boa_gc::Gc; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; +#[cfg(feature = "trace")] +use crate::vm::TraceFlags; + pub(crate) use function::FunctionCompiler; pub(crate) use jump_control::JumpControlInfo; use thin_vec::ThinVec; @@ -1550,6 +1553,8 @@ impl<'ctx> ByteCompiler<'ctx> { handlers: self.handlers, flags: Cell::new(self.code_block_flags), ic: self.ic.into_boxed_slice(), + #[cfg(feature = "trace")] + trace_flags: Cell::new(TraceFlags::empty()), } } diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 32df528b35d..a03545d6ac9 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -12,6 +12,10 @@ pub use icu::IcuError; use intrinsics::Intrinsics; use crate::vm::RuntimeLimits; + +#[cfg(feature = "trace")] +use crate::vm::trace; + use crate::{ builtins, class::{Class, ClassBuilder}, @@ -445,11 +449,25 @@ impl Context { &self.vm.realm } - /// Set the value of trace on the context #[cfg(feature = "trace")] #[inline] - pub fn set_trace(&mut self, trace: bool) { - self.vm.trace = trace; + /// Initializes the default `Vm` trace from the context + pub fn init_trace(&mut self) { + self.vm.trace.activate_trace(); + } + + #[cfg(feature = "trace")] + #[inline] + /// Initializes a partial `Vm` trace from the context. + pub fn init_partial_trace(&mut self) { + self.vm.trace.activate_partial_trace(); + } + + #[cfg(feature = "trace")] + /// Sets custom handling of trace messages. + pub fn set_tracer_implementation(&mut self, tracer: Box) { + self.init_trace(); + self.vm.trace.set_tracer(tracer); } /// Get optimizer options. diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 502f658c85f..555e1baa112 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -66,10 +66,19 @@ bitflags! { /// Arrow and method functions don't have `"prototype"` property. const HAS_PROTOTYPE_PROPERTY = 0b1000_0000; + } +} +#[cfg(feature = "trace")] +bitflags! { + /// Trace specific flags for [`CodeBlock`] + #[derive(Clone, Copy, Debug, Finalize)] + pub(crate) struct TraceFlags: u8 { /// Trace instruction execution to `stdout`. - #[cfg(feature = "trace")] - const TRACEABLE = 0b1000_0000_0000_0000; + const TRACEABLE = 0b0000_0001; + + /// Has the `CodeBlock` been traced. + const CALLFRAME_TRACED = 0b0000_0010; } } @@ -134,6 +143,10 @@ pub struct CodeBlock { #[unsafe_ignore_trace] pub(crate) flags: Cell, + #[cfg(feature = "trace")] + #[unsafe_ignore_trace] + pub(crate) trace_flags: Cell, + /// The number of arguments expected. pub(crate) length: u32, @@ -183,6 +196,8 @@ impl CodeBlock { params: FormalParameterList::default(), handlers: ThinVec::default(), ic: Box::default(), + #[cfg(feature = "trace")] + trace_flags: Cell::new(TraceFlags::empty()), } } @@ -195,15 +210,31 @@ impl CodeBlock { /// Check if the function is traced. #[cfg(feature = "trace")] pub(crate) fn traceable(&self) -> bool { - self.flags.get().contains(CodeBlockFlags::TRACEABLE) + self.trace_flags.get().contains(TraceFlags::TRACEABLE) } /// Enable or disable instruction tracing to `stdout`. #[cfg(feature = "trace")] #[inline] pub fn set_traceable(&self, value: bool) { - let mut flags = self.flags.get(); - flags.set(CodeBlockFlags::TRACEABLE, value); - self.flags.set(flags); + let mut flags = self.trace_flags.get(); + flags.set(TraceFlags::TRACEABLE, value); + self.trace_flags.set(flags); + } + + /// Check whether the frame has been traced. + #[cfg(feature = "trace")] + pub(crate) fn frame_traced(&self) -> bool { + self.trace_flags + .get() + .contains(TraceFlags::CALLFRAME_TRACED) + } + + /// Set the current frame as traced. + #[cfg(feature = "trace")] + pub(crate) fn set_frame_traced(&self, value: bool) { + let mut flags = self.trace_flags.get(); + flags.set(TraceFlags::CALLFRAME_TRACED, value); + self.trace_flags.set(flags); } /// Check if the function is a class constructor. diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 46fcdfc297d..6d4df57dde3 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -21,6 +21,9 @@ mod code_block; mod completion_record; mod inline_cache; mod opcode; +#[cfg(feature = "trace")] +pub mod trace; + mod runtime_limits; #[cfg(feature = "flowgraph")] @@ -28,6 +31,9 @@ pub mod flowgraph; pub(crate) use inline_cache::InlineCache; +#[cfg(feature = "trace")] +use trace::VmTrace; + // TODO: see if this can be exposed on all features. #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode, VaryingOperandKind}; @@ -46,6 +52,9 @@ pub(crate) use { opcode::BindingOpcode, }; +#[cfg(feature = "trace")] +pub(crate) use code_block::TraceFlags; + #[cfg(test)] mod tests; @@ -76,7 +85,7 @@ pub struct Vm { pub(crate) realm: Realm, #[cfg(feature = "trace")] - pub(crate) trace: bool, + pub(crate) trace: VmTrace, } /// Active runnable in the current vm context. @@ -108,7 +117,7 @@ impl Vm { native_active_function: None, realm, #[cfg(feature = "trace")] - trace: false, + trace: VmTrace::default(), } } @@ -264,38 +273,6 @@ pub(crate) enum CompletionType { #[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; - - pub(crate) fn trace_call_frame(&self) { - let msg = if self.vm.frames.last().is_some() { - format!( - " Call Frame -- {} ", - self.vm.frame().code_block().name().to_std_string_escaped() - ) - } else { - " VM Start ".to_string() - }; - - println!("{}", self.vm.frame().code_block); - println!( - "{msg:-^width$}", - width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 - ); - println!( - "{:(&mut self, f: F) -> JsResult where F: FnOnce(Opcode, &mut Context) -> JsResult, @@ -368,14 +345,7 @@ impl Context { VaryingOperandKind::U32 => ".U32", }; - println!( - "{: { let mut fp = self.vm.frame().fp(); let mut env_fp = self.vm.frame().env_fp; + #[cfg(feature = "trace")] + self.vm.trace.trace_frame_end(&self.vm, "Throw"); if self.vm.frame().exit_early() { self.vm.environments.truncate(env_fp as usize); self.vm.stack.truncate(fp as usize); @@ -519,6 +496,9 @@ impl Context { } // Early return immediately. CompletionType::Yield => { + #[cfg(feature = "trace")] + self.vm.trace.trace_frame_end(&self.vm, "Yield"); + let result = self.vm.take_return_value(); if self.vm.frame().exit_early() { return ControlFlow::Break(CompletionRecord::Return(result)); @@ -539,9 +519,7 @@ impl Context { let _timer = Profiler::global().start_event("run_async_with_budget", "vm"); #[cfg(feature = "trace")] - if self.vm.trace { - self.trace_call_frame(); - } + self.vm.trace.trace_call_frame(&self.vm); let mut runtime_budget: u32 = budget; @@ -564,9 +542,7 @@ impl Context { let _timer = Profiler::global().start_event("run", "vm"); #[cfg(feature = "trace")] - if self.vm.trace { - self.trace_call_frame(); - } + self.vm.trace.trace_call_frame(&self.vm); loop { match self.execute_one(Opcode::execute) { diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs new file mode 100644 index 00000000000..4397d4a5126 --- /dev/null +++ b/core/engine/src/vm/trace.rs @@ -0,0 +1,285 @@ +//! Boa's `Trace` module for the `Vm`. + +use bitflags::bitflags; +use std::cell::Cell; +use std::collections::VecDeque; +use std::fmt; + +use super::{Vm, Constant}; + +// TODO: Build out further, maybe provide more element visiblity and events/outputs +/// The `Tracer` trait is a customizable trait that can be provided to `Boa` +/// for customizing output. +pub trait Tracer { + /// The output from tracing a `CodeBlock`'s bytecode. + fn emit_bytecode_trace(&self, msg: &str); + /// The output from entering a `CallFrame`. + fn emit_call_frame_entrance_trace(&self, msg: &str); + /// The trace output from an execution. + fn emit_instruction_trace(&self, msg: &str); + /// Trace output from exiting a `CallFrame`. + fn emit_call_frame_exit_trace(&self, msg: &str); +} + +#[derive(Debug)] +pub(crate) struct ActiveTracer; + +impl Tracer for ActiveTracer { + fn emit_bytecode_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_call_frame_entrance_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_instruction_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_call_frame_exit_trace(&self, msg: &str) { + println!("{msg}"); + } +} + +pub(crate) struct DefaultTracer; + +impl Tracer for DefaultTracer { + fn emit_bytecode_trace(&self, _: &str) {} + + fn emit_call_frame_entrance_trace(&self, _: &str) {} + + fn emit_instruction_trace(&self, _: &str) {} + + fn emit_call_frame_exit_trace(&self, _: &str) {} +} + +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub(crate) struct TraceOptions: u8 { + const FULL_TRACE = 0b0000_0001; + + const ACTIVE = 0b0000_0010; + } +} + +impl Default for VmTrace { + fn default() -> Self { + Self { + options: Cell::new(TraceOptions::empty()), + tracer: Box::new(DefaultTracer), + } + } +} + +/// `VmTrace` is a boa spcific structure for running Boa's Virtual Machine trace. +/// +/// The struct provides options for a user to set customized actions for handling +/// messages output during the trace. +/// +/// Currently, the trace supports setting two different actions: +/// - `compiled_action` +/// - `trace_action` +/// +/// About the actions +/// +/// After the Global callframe is initially provided. It searches +/// for all possible compiled output +pub struct VmTrace { + options: Cell, + tracer: Box, +} + +// ==== Public API ==== + +impl VmTrace { + #[must_use] + /// Create a partial `VmTrace`. + pub fn partial() -> Self { + Self { + options: Cell::new(TraceOptions::empty()), + tracer: Box::new(ActiveTracer), + } + } + + /// Method for adding a compiled action on initialization. + pub fn set_tracer(&mut self, tracer: Box) { + self.tracer = tracer; + } + + /// Sets the current `Tracer` of `VmTrace`. + pub fn activate_trace(&mut self) { + self.options.set(TraceOptions::FULL_TRACE); + self.tracer = Box::new(ActiveTracer); + } + + pub(crate) fn activate_partial_trace(&mut self) { + self.tracer = Box::new(ActiveTracer); + } +} + +// ==== Internal VmTrace methods ==== + +impl VmTrace { + /// Returns if Trace type is a complete trace. + pub(crate) fn is_full_trace(&self) -> bool { + self.options.get().contains(TraceOptions::FULL_TRACE) + } + + /// Returns if the trace is only a partial one. + pub fn is_partial_trace(&self) -> bool { + !self.is_full_trace() + } + + /// Returns if the a partial trace has been determined to be active. + pub fn is_active(&self) -> bool { + self.options.get().contains(TraceOptions::ACTIVE) + } + + /// Sets the `ACTIVE` bitflag to true. + pub(crate) fn activate(&self) { + let mut flags = self.options.get(); + flags.set(TraceOptions::ACTIVE, true); + self.options.set(flags); + } + + /// Sets the `ACTIVE` flag to false. + pub(crate) fn inactivate(&self) { + let mut flags = self.options.get(); + flags.set(TraceOptions::ACTIVE, false); + self.options.set(flags); + } + + /// Returns whether a trace should run on an instruction. + pub(crate) fn should_trace(&self) -> bool { + self.is_full_trace() || self.is_active() + } +} + +// ==== Trace Event/Action Methods ==== + +impl VmTrace { + 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; + + /// Trace the current `CallFrame` according to current state + pub(crate) fn trace_call_frame(&self, vm: &Vm) { + if self.is_full_trace() { + self.trace_compiled_bytecode(vm); + self.call_frame_header(vm); + } else if self.is_partial_trace() && vm.frame().code_block().traceable() { + if !vm.frame().code_block().frame_traced() { + self.trace_current_bytecode(vm); + vm.frame().code_block().set_frame_traced(true); + } + self.call_frame_header(vm); + self.activate(); + } else { + self.call_frame_header(vm); + } + } + + /// Emits the current `CallFrame`'s header. + pub(crate) fn call_frame_header(&self, vm: &Vm) { + let msg = format!( + " Call Frame -- {} ", + vm.frame().code_block().name().to_std_string_escaped() + ); + + let frame_header = format!( + "{msg:-^width$}", + width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 + ); + self.tracer.emit_call_frame_entrance_trace(&frame_header); + + if vm.frames.len() == 1 { + let column_headers = format!( + "{: queue.push_back(block.clone()), + _=>{}, + } + } + + self.tracer + .emit_bytecode_trace(&block.to_string()); + } + } + } + + /// Searches and traces for only current frame's `CodeBlock`. + pub(crate) fn trace_current_bytecode(&self, vm: &Vm) { + self.tracer + .emit_bytecode_trace(&vm.frame().code_block().to_string()); + } + + /// Emits an exit message for the current `CallFrame`. + pub(crate) fn trace_frame_end(&self, vm: &Vm, return_msg: &str) { + if self.should_trace() { + let msg = format!( + " Call Frame -- ", + vm.frame().code_block().name.to_std_string_escaped() + ); + let frame_footer = format!( + "{msg:-^width$}", + width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 + ); + + self.tracer.emit_call_frame_exit_trace(&frame_footer); + } + + self.inactivate(); + } + + pub(crate) fn trace_instruction( + &self, + duration: u128, + operand_kind: &str, + opcode: &str, + operands: &str, + stack: &str, + ) { + let instruction_trace = format!( + "{:) -> fmt::Result { + write!(f, "Current Active Tracer") + } +} diff --git a/ffi/wasm/Cargo.toml b/ffi/wasm/Cargo.toml index 2445c26a696..b1db23c1a2f 100644 --- a/ffi/wasm/Cargo.toml +++ b/ffi/wasm/Cargo.toml @@ -12,10 +12,11 @@ repository.workspace = true rust-version.workspace = true [dependencies] -boa_engine = { workspace = true, features = ["js"] } +boa_engine = { workspace = true, features = ["js", "trace"] } wasm-bindgen = { version = "0.2.91", default-features = false } getrandom = { version = "0.2.14", features = ["js"] } console_error_panic_hook = "0.1.7" +js-sys = "0.3.66" [dev-dependencies] wasm-bindgen-test = "0.3.42" diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index 16a6974f6ef..978ed3d6249 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -2,9 +2,10 @@ #![cfg_attr(not(test), forbid(clippy::unwrap_used))] #![allow(unused_crate_dependencies)] -use boa_engine::{Context, Source}; +use boa_engine::{Context, Source, vm::trace::Tracer}; use getrandom as _; use wasm_bindgen::prelude::*; +use js_sys; #[wasm_bindgen(start)] fn main_js() { @@ -24,3 +25,160 @@ pub fn evaluate(src: &str) -> Result { .map_err(|e| JsValue::from(format!("Uncaught {e}"))) .map(|v| v.display().to_string()) } + +#[wasm_bindgen] +/// Evaluate some JavaScript with trace hooks. +pub fn evaluate_with_debug_hooks( + src: &str, + compiled_output_action: &js_sys::Function, + trace_output_action: &js_sys::Function, +) -> Result { + let compiled_clone = compiled_output_action.clone(); + let compiled_action = move |output: &str| { + let this = JsValue::null(); + let o = JsValue::from(output); + let _unused = compiled_clone.call1(&this, &o); + }; + + let trace_clone = trace_output_action.clone(); + let trace_action = move |output: &str| { + let this = JsValue::null(); + let o = JsValue::from(output); + let _unused = trace_clone.call1(&this, &o); + }; + + // setup executor + let mut context = Context::default(); + let mut tracer = WasmTracer::default(); + tracer.set_compiled_handler(Box::new(compiled_action)); + tracer.set_trace_handler(Box::new(trace_action)); + + context.set_tracer_implementation(Box::new(tracer)); + + context + .eval(Source::from_bytes(src)) + .map_err(|e| JsValue::from(format!("Uncaught {e}"))) + .map(|v| v.display().to_string()) +} + +#[derive(Debug)] +#[wasm_bindgen] +/// The WASM exposed `BoaJs` Object. +pub struct BoaJs { + compiled_action: Option, + trace_action: Option, +} + +#[wasm_bindgen] +impl BoaJs { + /// Create a new BoaJs Object. + #[must_use] + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + compiled_action: None, + trace_action: None, + } + } + + /// Set a Js Closure action for handling Boa's ByteCompiler trace output. + pub fn set_compiled_output_action(&mut self, f: &js_sys::Function) { + let fun = f.clone(); + self.compiled_action = Some(fun); + } + + /// Set a Js Closure action for handling Boa's VM Trace output. + pub fn set_trace_output_action(&mut self, f: &js_sys::Function) { + let fun = f.clone(); + self.trace_action = Some(fun); + } + + /// Evaluate some Js Source Code with trace active. + pub fn evaluate_with_trace(&self, src: &str) -> Result { + // setup executor + let mut context = Context::default(); + + let mut tracer = WasmTracer::default(); + + if let Some(func) = &self.compiled_action { + let func_clone = func.clone(); + let action = move |output: &str| { + let this = JsValue::null(); + let o = JsValue::from(output); + let _unused = func_clone.call1(&this, &o); + }; + + tracer.set_compiled_handler(Box::new(action)); + }; + + if let Some(func) = &self.trace_action { + let func_clone = func.clone(); + let action = move |output: &str| { + let this = JsValue::null(); + let o = JsValue::from(output); + let _unused = func_clone.call1(&this, &o); + }; + + tracer.set_trace_handler(Box::new(action)); + }; + + context.set_tracer_implementation(Box::new(tracer)); + + context + .eval(Source::from_bytes(src)) + .map_err(|e| JsValue::from(format!("Uncaught {e}"))) + .map(|v| v.display().to_string()) + } + + /// Evaluate Js Source code without running trace. + pub fn evaluate(&self, src: &str) -> Result { + Context::default() + .eval(Source::from_bytes(src)) + .map_err(|e| JsValue::from(format!("Uncaught {e}"))) + .map(|v| v.display().to_string()) + } +} + +type ProvidedFunction = Box; + +#[derive(Default)] +pub(crate) struct WasmTracer { + compiled_handler: Option, + trace_handler: Option, +} + +impl WasmTracer { + fn set_compiled_handler(&mut self, compiled_handler: Box) { + self.compiled_handler = Some(compiled_handler); + } + + fn set_trace_handler(&mut self, trace_handler: Box) { + self.trace_handler = Some(trace_handler); + } +} + +impl Tracer for WasmTracer { + fn emit_bytecode_trace(&self, msg: &str) { + if let Some(action) = &self.compiled_handler { + action(msg); + } + } + + fn emit_call_frame_entrance_trace(&self, msg: &str) { + if let Some(action) = &self.trace_handler { + action(msg); + } + } + + fn emit_instruction_trace(&self, msg: &str) { + if let Some(action) = &self.trace_handler { + action(msg); + } + } + + fn emit_call_frame_exit_trace(&self, msg: &str) { + if let Some(action) = &self.trace_handler { + action(msg); + } + } +} From a1900c8a56265c3fa9992819910a53e99c8c70c9 Mon Sep 17 00:00:00 2001 From: nekevss Date: Wed, 6 Dec 2023 21:07:58 -0500 Subject: [PATCH 02/11] Post-rebase fmt --- core/engine/src/vm/mod.rs | 8 +++++++- core/engine/src/vm/trace.rs | 7 +++---- ffi/wasm/src/lib.rs | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 6d4df57dde3..b456ac27c42 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -345,7 +345,13 @@ impl Context { VaryingOperandKind::U32 => ".U32", }; - self.vm.trace.trace_instruction(duration.as_micros(), varying_operand_kind, opcode.as_str(), &operands, &stack); + self.vm.trace.trace_instruction( + duration.as_micros(), + varying_operand_kind, + opcode.as_str(), + &operands, + &stack, + ); result } diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 4397d4a5126..1718785a462 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use std::collections::VecDeque; use std::fmt; -use super::{Vm, Constant}; +use super::{Constant, Vm}; // TODO: Build out further, maybe provide more element visiblity and events/outputs /// The `Tracer` trait is a customizable trait that can be provided to `Boa` @@ -223,12 +223,11 @@ impl VmTrace { for constant in &block.constants { match constant { Constant::Function(block) => queue.push_back(block.clone()), - _=>{}, + _ => {} } } - self.tracer - .emit_bytecode_trace(&block.to_string()); + self.tracer.emit_bytecode_trace(&block.to_string()); } } } diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index 978ed3d6249..6354d5a3c39 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -4,8 +4,8 @@ use boa_engine::{Context, Source, vm::trace::Tracer}; use getrandom as _; -use wasm_bindgen::prelude::*; use js_sys; +use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] fn main_js() { From 2ae0204f56d0e46b7838a6e74120dfb4e6b7c020 Mon Sep 17 00:00:00 2001 From: nekevss Date: Wed, 6 Dec 2023 21:47:59 -0500 Subject: [PATCH 03/11] Clean up logic for clarity --- core/engine/src/vm/trace.rs | 22 ++++++++++------------ ffi/wasm/src/lib.rs | 14 +++++++++++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 1718785a462..126cb8e2c5b 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -169,17 +169,14 @@ impl VmTrace { pub(crate) fn trace_call_frame(&self, vm: &Vm) { if self.is_full_trace() { self.trace_compiled_bytecode(vm); - self.call_frame_header(vm); - } else if self.is_partial_trace() && vm.frame().code_block().traceable() { + } else if vm.frame().code_block().traceable() { if !vm.frame().code_block().frame_traced() { self.trace_current_bytecode(vm); - vm.frame().code_block().set_frame_traced(true); } - self.call_frame_header(vm); self.activate(); - } else { - self.call_frame_header(vm); } + + self.call_frame_header(vm); } /// Emits the current `CallFrame`'s header. @@ -218,16 +215,16 @@ impl VmTrace { queue.push_back(vm.frame().code_block.clone()); while !queue.is_empty() { - let block = queue.pop_front().expect("queue must have a value."); + let active_block = queue.pop_front().expect("queue must have a value."); - for constant in &block.constants { - match constant { - Constant::Function(block) => queue.push_back(block.clone()), - _ => {} + for constant in &active_block.constants { + if let Constant::Function(block) = constant { + queue.push_back(block.clone()); } } - self.tracer.emit_bytecode_trace(&block.to_string()); + self.tracer.emit_bytecode_trace(&active_block.to_string()); + active_block.set_frame_traced(true); } } } @@ -236,6 +233,7 @@ impl VmTrace { pub(crate) fn trace_current_bytecode(&self, vm: &Vm) { self.tracer .emit_bytecode_trace(&vm.frame().code_block().to_string()); + vm.frame().code_block().set_frame_traced(true); } /// Emits an exit message for the current `CallFrame`. diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index 6354d5a3c39..fe8e0dfa507 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -4,7 +4,6 @@ use boa_engine::{Context, Source, vm::trace::Tracer}; use getrandom as _; -use js_sys; use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] @@ -28,6 +27,10 @@ pub fn evaluate(src: &str) -> Result { #[wasm_bindgen] /// Evaluate some JavaScript with trace hooks. +/// +/// # Errors +/// +/// If the execution of the script throws, returns a `JsValue` with the error string. pub fn evaluate_with_debug_hooks( src: &str, compiled_output_action: &js_sys::Function, @@ -74,6 +77,7 @@ impl BoaJs { /// Create a new BoaJs Object. #[must_use] #[wasm_bindgen(constructor)] + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { compiled_action: None, @@ -94,6 +98,10 @@ impl BoaJs { } /// Evaluate some Js Source Code with trace active. + /// + /// # Errors + /// + /// If the execution of the script throws, returns a `JsValue` with the error string. pub fn evaluate_with_trace(&self, src: &str) -> Result { // setup executor let mut context = Context::default(); @@ -131,6 +139,10 @@ impl BoaJs { } /// Evaluate Js Source code without running trace. + /// + /// # Errors + /// + /// If the execution of the script throws, returns a `JsValue` with the error string. pub fn evaluate(&self, src: &str) -> Result { Context::default() .eval(Source::from_bytes(src)) From e0e64d24a19148d32d6d739e8ac0f57ccb6c325b Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 8 Dec 2023 14:06:04 -0500 Subject: [PATCH 04/11] Review feedback --- core/engine/src/bytecompiler/mod.rs | 5 ---- core/engine/src/vm/code_block.rs | 43 ++++++++++------------------- core/engine/src/vm/mod.rs | 3 -- core/engine/src/vm/trace.rs | 6 ++-- 4 files changed, 17 insertions(+), 40 deletions(-) diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 0740b5dae76..13b8867882f 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -42,9 +42,6 @@ use boa_gc::Gc; use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; -#[cfg(feature = "trace")] -use crate::vm::TraceFlags; - pub(crate) use function::FunctionCompiler; pub(crate) use jump_control::JumpControlInfo; use thin_vec::ThinVec; @@ -1553,8 +1550,6 @@ impl<'ctx> ByteCompiler<'ctx> { handlers: self.handlers, flags: Cell::new(self.code_block_flags), ic: self.ic.into_boxed_slice(), - #[cfg(feature = "trace")] - trace_flags: Cell::new(TraceFlags::empty()), } } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 555e1baa112..ffd54cc3ba2 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -66,19 +66,14 @@ bitflags! { /// Arrow and method functions don't have `"prototype"` property. const HAS_PROTOTYPE_PROPERTY = 0b1000_0000; - } -} -#[cfg(feature = "trace")] -bitflags! { - /// Trace specific flags for [`CodeBlock`] - #[derive(Clone, Copy, Debug, Finalize)] - pub(crate) struct TraceFlags: u8 { /// Trace instruction execution to `stdout`. - const TRACEABLE = 0b0000_0001; + #[cfg(feature = "trace")] + const TRACEABLE = 0b0100_0000_0000_0000; - /// Has the `CodeBlock` been traced. - const CALLFRAME_TRACED = 0b0000_0010; + /// Stores whether the `CodeBlock` been traced. + #[cfg(feature = "trace")] + const WAS_TRACED = 0b1000_0000_0000_0000; } } @@ -143,10 +138,6 @@ pub struct CodeBlock { #[unsafe_ignore_trace] pub(crate) flags: Cell, - #[cfg(feature = "trace")] - #[unsafe_ignore_trace] - pub(crate) trace_flags: Cell, - /// The number of arguments expected. pub(crate) length: u32, @@ -196,8 +187,6 @@ impl CodeBlock { params: FormalParameterList::default(), handlers: ThinVec::default(), ic: Box::default(), - #[cfg(feature = "trace")] - trace_flags: Cell::new(TraceFlags::empty()), } } @@ -210,31 +199,29 @@ impl CodeBlock { /// Check if the function is traced. #[cfg(feature = "trace")] pub(crate) fn traceable(&self) -> bool { - self.trace_flags.get().contains(TraceFlags::TRACEABLE) + self.flags.get().contains(CodeBlockFlags::TRACEABLE) } /// Enable or disable instruction tracing to `stdout`. #[cfg(feature = "trace")] #[inline] pub fn set_traceable(&self, value: bool) { - let mut flags = self.trace_flags.get(); - flags.set(TraceFlags::TRACEABLE, value); - self.trace_flags.set(flags); + let mut flags = self.flags.get(); + flags.set(CodeBlockFlags::TRACEABLE, value); + self.flags.set(flags); } - /// Check whether the frame has been traced. + /// Returns whether the frame has been previously traced. #[cfg(feature = "trace")] - pub(crate) fn frame_traced(&self) -> bool { - self.trace_flags - .get() - .contains(TraceFlags::CALLFRAME_TRACED) + pub(crate) fn was_traced(&self) -> bool { + self.flags.get().contains(CodeBlockFlags::WAS_TRACED) } /// Set the current frame as traced. #[cfg(feature = "trace")] pub(crate) fn set_frame_traced(&self, value: bool) { - let mut flags = self.trace_flags.get(); - flags.set(TraceFlags::CALLFRAME_TRACED, value); - self.trace_flags.set(flags); + let mut flags = self.flags.get(); + flags.set(CodeBlockFlags::WAS_TRACED, value); + self.flags.set(flags); } /// Check if the function is a class constructor. diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index b456ac27c42..825b666a67b 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -52,9 +52,6 @@ pub(crate) use { opcode::BindingOpcode, }; -#[cfg(feature = "trace")] -pub(crate) use code_block::TraceFlags; - #[cfg(test)] mod tests; diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 126cb8e2c5b..06d015f3292 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -170,7 +170,7 @@ impl VmTrace { if self.is_full_trace() { self.trace_compiled_bytecode(vm); } else if vm.frame().code_block().traceable() { - if !vm.frame().code_block().frame_traced() { + if !vm.frame().code_block().was_traced() { self.trace_current_bytecode(vm); } self.activate(); @@ -214,9 +214,7 @@ impl VmTrace { let mut queue = VecDeque::new(); queue.push_back(vm.frame().code_block.clone()); - while !queue.is_empty() { - let active_block = queue.pop_front().expect("queue must have a value."); - + while let Some(active_block) = queue.pop_front() { for constant in &active_block.constants { if let Constant::Function(block) = constant { queue.push_back(block.clone()); From a07ba2a18f5f7708fa365feacb7d3d38e5915bd8 Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 8 Dec 2023 15:40:41 -0500 Subject: [PATCH 05/11] Add tracer name and fix/clean some logic --- core/engine/src/vm/mod.rs | 20 +++--- core/engine/src/vm/trace.rs | 120 ++++++++++++++++++++---------------- ffi/wasm/src/lib.rs | 4 ++ 3 files changed, 82 insertions(+), 62 deletions(-) diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 825b666a67b..818c9d7473c 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -38,6 +38,8 @@ use trace::VmTrace; #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode, VaryingOperandKind}; pub use runtime_limits::RuntimeLimits; + +use self::trace::TraceAction; pub use { call_frame::{CallFrame, GeneratorResumeKind}, code_block::CodeBlock, @@ -187,6 +189,9 @@ impl Vm { } self.frames.push(frame); + + #[cfg(feature = "trace")] + self.trace.trace_call_frame(self); } pub(crate) fn push_frame_with_stack( @@ -199,6 +204,9 @@ impl Vm { self.push(function); self.push_frame(frame); + + #[cfg(feature = "trace")] + self.trace.trace_call_frame(self); } pub(crate) fn pop_frame(&mut self) -> Option { @@ -390,10 +398,10 @@ impl Context { } #[cfg(feature = "trace")] - let result = if self.vm.trace.should_trace() { - self.trace_execute_instruction(f) - } else { + let result = if self.vm.trace.should_trace(&self.vm) == TraceAction::None { self.execute_instruction(f) + } else { + self.trace_execute_instruction(f) }; #[cfg(not(feature = "trace"))] @@ -521,9 +529,6 @@ impl Context { pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord { let _timer = Profiler::global().start_event("run_async_with_budget", "vm"); - #[cfg(feature = "trace")] - self.vm.trace.trace_call_frame(&self.vm); - let mut runtime_budget: u32 = budget; loop { @@ -544,9 +549,6 @@ impl Context { pub(crate) fn run(&mut self) -> CompletionRecord { let _timer = Profiler::global().start_event("run", "vm"); - #[cfg(feature = "trace")] - self.vm.trace.trace_call_frame(&self.vm); - loop { match self.execute_one(Opcode::execute) { ControlFlow::Continue(()) => {} diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 06d015f3292..d8a44f18282 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -19,6 +19,8 @@ pub trait Tracer { fn emit_instruction_trace(&self, msg: &str); /// Trace output from exiting a `CallFrame`. fn emit_call_frame_exit_trace(&self, msg: &str); + /// Tracer name + fn name(&self) -> &str; } #[derive(Debug)] @@ -40,6 +42,10 @@ impl Tracer for ActiveTracer { fn emit_call_frame_exit_trace(&self, msg: &str) { println!("{msg}"); } + + fn name(&self) -> &str { + "Default Active Trace" + } } pub(crate) struct DefaultTracer; @@ -52,14 +58,17 @@ impl Tracer for DefaultTracer { fn emit_instruction_trace(&self, _: &str) {} fn emit_call_frame_exit_trace(&self, _: &str) {} + + fn name(&self) -> &str { + "Default Empty Trace" + } } bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct TraceOptions: u8 { + // Whether the trace is a full trace. const FULL_TRACE = 0b0000_0001; - - const ACTIVE = 0b0000_0010; } } @@ -113,7 +122,8 @@ impl VmTrace { self.tracer = Box::new(ActiveTracer); } - pub(crate) fn activate_partial_trace(&mut self) { + /// Sets `VmTrace`s tracer to an active tracer. + pub fn activate_partial_trace(&mut self) { self.tracer = Box::new(ActiveTracer); } } @@ -126,34 +136,38 @@ impl VmTrace { self.options.get().contains(TraceOptions::FULL_TRACE) } - /// Returns if the trace is only a partial one. - pub fn is_partial_trace(&self) -> bool { - !self.is_full_trace() - } - - /// Returns if the a partial trace has been determined to be active. - pub fn is_active(&self) -> bool { - self.options.get().contains(TraceOptions::ACTIVE) - } - - /// Sets the `ACTIVE` bitflag to true. - pub(crate) fn activate(&self) { - let mut flags = self.options.get(); - flags.set(TraceOptions::ACTIVE, true); - self.options.set(flags); - } - - /// Sets the `ACTIVE` flag to false. - pub(crate) fn inactivate(&self) { - let mut flags = self.options.get(); - flags.set(TraceOptions::ACTIVE, false); - self.options.set(flags); + /// Returns how the trace should behave with the given context. + pub(crate) fn should_trace(&self, vm: &Vm) -> TraceAction { + // Check if is no trace or the block is not traceable. + if !self.is_full_trace() && !vm.frame().code_block().traceable() { + return TraceAction::None; + // Check if the block is a full trace. + } else if self.is_full_trace() { + // The bytecode should only be traced on the first callframe. + if vm.frames.len() == 1 { + return TraceAction::FullWithBytecode; + } + return TraceAction::Full; + // If the block was previously traced, we should not emit the bytecode again. + } else if vm.frame().code_block().was_traced() { + return TraceAction::Block; + } + TraceAction::BlockWithBytecode } +} - /// Returns whether a trace should run on an instruction. - pub(crate) fn should_trace(&self) -> bool { - self.is_full_trace() || self.is_active() - } +#[derive(Debug, Eq, PartialEq)] +pub(crate) enum TraceAction { + // No trace + None = 0, + // Full trace + Full, + // Full trace with bytecode + FullWithBytecode, + // Partial codeblock trace + Block, + // Partial codeblock with bytecode + BlockWithBytecode, } // ==== Trace Event/Action Methods ==== @@ -167,16 +181,21 @@ impl VmTrace { /// Trace the current `CallFrame` according to current state pub(crate) fn trace_call_frame(&self, vm: &Vm) { - if self.is_full_trace() { - self.trace_compiled_bytecode(vm); - } else if vm.frame().code_block().traceable() { - if !vm.frame().code_block().was_traced() { + let action = self.should_trace(vm); + match action { + TraceAction::Full | TraceAction::Block => { + self.call_frame_header(vm); + } + TraceAction::FullWithBytecode => { + self.trace_compiled_bytecode(vm); + self.call_frame_header(vm); + } + TraceAction::BlockWithBytecode => { self.trace_current_bytecode(vm); + self.call_frame_header(vm); } - self.activate(); + TraceAction::None => {} } - - self.call_frame_header(vm); } /// Emits the current `CallFrame`'s header. @@ -209,21 +228,18 @@ impl VmTrace { /// Searches traces all of the current `CallFrame`'s available `CodeBlock`s. pub(crate) fn trace_compiled_bytecode(&self, vm: &Vm) { - // We only continue to the compiled output if we are on the global. - if vm.frames.len() == 1 { - let mut queue = VecDeque::new(); - queue.push_back(vm.frame().code_block.clone()); - - while let Some(active_block) = queue.pop_front() { - for constant in &active_block.constants { - if let Constant::Function(block) = constant { - queue.push_back(block.clone()); - } - } + let mut queue = VecDeque::new(); + queue.push_back(vm.frame().code_block.clone()); - self.tracer.emit_bytecode_trace(&active_block.to_string()); - active_block.set_frame_traced(true); + while let Some(active_block) = queue.pop_front() { + for constant in &active_block.constants { + if let Constant::Function(block) = constant { + queue.push_back(block.clone()); + } } + + self.tracer.emit_bytecode_trace(&active_block.to_string()); + active_block.set_frame_traced(true); } } @@ -236,7 +252,7 @@ impl VmTrace { /// Emits an exit message for the current `CallFrame`. pub(crate) fn trace_frame_end(&self, vm: &Vm, return_msg: &str) { - if self.should_trace() { + if self.should_trace(vm) != TraceAction::None { let msg = format!( " Call Frame -- ", vm.frame().code_block().name.to_std_string_escaped() @@ -248,8 +264,6 @@ impl VmTrace { self.tracer.emit_call_frame_exit_trace(&frame_footer); } - - self.inactivate(); } pub(crate) fn trace_instruction( @@ -275,6 +289,6 @@ impl VmTrace { impl fmt::Debug for VmTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Current Active Tracer") + write!(f, "{}", self.tracer.name()) } } diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index fe8e0dfa507..a177f8f4e6a 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -193,4 +193,8 @@ impl Tracer for WasmTracer { action(msg); } } + + fn name(&self) -> &str { + "WASM Debugger Trace" + } } From 8099c04d54a8ca55498a596fe21c0ba71cef0786 Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 8 Dec 2023 15:44:52 -0500 Subject: [PATCH 06/11] Fix TraceAction import --- core/engine/src/vm/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 818c9d7473c..ae5d0766cd7 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -32,14 +32,13 @@ pub mod flowgraph; pub(crate) use inline_cache::InlineCache; #[cfg(feature = "trace")] -use trace::VmTrace; +use trace::{VmTrace, TraceAction}; // TODO: see if this can be exposed on all features. #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode, VaryingOperandKind}; pub use runtime_limits::RuntimeLimits; -use self::trace::TraceAction; pub use { call_frame::{CallFrame, GeneratorResumeKind}, code_block::CodeBlock, From 39f973e85d360782dbeb54ed40c3c9629caf64b8 Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 8 Dec 2023 15:48:40 -0500 Subject: [PATCH 07/11] cargo fmt all --- core/engine/src/vm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index ae5d0766cd7..d65cffbebe7 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -32,7 +32,7 @@ pub mod flowgraph; pub(crate) use inline_cache::InlineCache; #[cfg(feature = "trace")] -use trace::{VmTrace, TraceAction}; +use trace::{TraceAction, VmTrace}; // TODO: see if this can be exposed on all features. #[allow(unused_imports)] From ed1cf6c65bbf2aa03b842a6bcfcb1bffb665585e Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 19 Jan 2024 18:03:53 -0500 Subject: [PATCH 08/11] Redesign for Vec> --- cli/src/debug/function.rs | 39 ++++++- core/engine/src/context/mod.rs | 9 +- core/engine/src/vm/code_block.rs | 6 +- core/engine/src/vm/mod.rs | 8 +- core/engine/src/vm/trace.rs | 172 +++++++++++-------------------- ffi/wasm/src/lib.rs | 11 +- 6 files changed, 110 insertions(+), 135 deletions(-) diff --git a/cli/src/debug/function.rs b/cli/src/debug/function.rs index 96e2a7c6f5f..ea97b61b665 100644 --- a/cli/src/debug/function.rs +++ b/cli/src/debug/function.rs @@ -2,7 +2,7 @@ use boa_engine::{ builtins::function::OrdinaryFunction, js_string, object::ObjectInitializer, - vm::flowgraph::{Direction, Graph}, + vm::{flowgraph::{Direction, Graph}, trace::{Tracer, TraceAction}}, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, }; @@ -121,6 +121,39 @@ fn bytecode(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult Ok(js_string!(code.to_string()).into()) } +// ==== Trace functionality ==== + +#[derive(Debug)] +struct FunctionTracer; + +impl Tracer for FunctionTracer { + fn should_trace(&self, frame: &boa_engine::vm::CallFrame) -> TraceAction { + if frame.code_block().traceable() { + if frame.code_block().was_traced() { + return TraceAction::Block + } + return TraceAction::BlockWithBytecode + } + TraceAction::None + } + + fn emit_bytecode_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_call_frame_entrance_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_call_frame_exit_trace(&self, msg: &str) { + println!("{msg}"); + } + + fn emit_instruction_trace(&self, msg: &str) { + println!("{msg}"); + } +} + fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult<()> { let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() @@ -145,7 +178,7 @@ fn trace(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult JsResult) { - self.init_trace(); + // self.init_trace(); self.vm.trace.set_tracer(tracer); } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index ffd54cc3ba2..d74b2c93bfa 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -198,7 +198,7 @@ impl CodeBlock { /// Check if the function is traced. #[cfg(feature = "trace")] - pub(crate) fn traceable(&self) -> bool { + pub fn traceable(&self) -> bool { self.flags.get().contains(CodeBlockFlags::TRACEABLE) } /// Enable or disable instruction tracing to `stdout`. @@ -212,13 +212,13 @@ impl CodeBlock { /// Returns whether the frame has been previously traced. #[cfg(feature = "trace")] - pub(crate) fn was_traced(&self) -> bool { + pub fn was_traced(&self) -> bool { self.flags.get().contains(CodeBlockFlags::WAS_TRACED) } /// Set the current frame as traced. #[cfg(feature = "trace")] - pub(crate) fn set_frame_traced(&self, value: bool) { + pub fn set_frame_traced(&self, value: bool) { let mut flags = self.flags.get(); flags.set(CodeBlockFlags::WAS_TRACED, value); self.flags.set(flags); diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index d65cffbebe7..729dba46db9 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -32,7 +32,7 @@ pub mod flowgraph; pub(crate) use inline_cache::InlineCache; #[cfg(feature = "trace")] -use trace::{TraceAction, VmTrace}; +use trace::VmTrace; // TODO: see if this can be exposed on all features. #[allow(unused_imports)] @@ -397,10 +397,10 @@ impl Context { } #[cfg(feature = "trace")] - let result = if self.vm.trace.should_trace(&self.vm) == TraceAction::None { - self.execute_instruction(f) - } else { + let result = if self.vm.trace.should_trace(&self.vm.frame()) { self.trace_execute_instruction(f) + } else { + self.execute_instruction(f) }; #[cfg(not(feature = "trace"))] diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index d8a44f18282..2b20159ec1b 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -1,16 +1,21 @@ //! Boa's `Trace` module for the `Vm`. -use bitflags::bitflags; -use std::cell::Cell; use std::collections::VecDeque; use std::fmt; -use super::{Constant, Vm}; +use super::{Constant, Vm, CallFrame}; // TODO: Build out further, maybe provide more element visiblity and events/outputs /// The `Tracer` trait is a customizable trait that can be provided to `Boa` /// for customizing output. -pub trait Tracer { +pub trait Tracer: fmt::Debug { + /// Whether the current call frame should trace. + fn should_trace(&self, frame: &CallFrame) -> TraceAction { + if frame.code_block.name().to_std_string_escaped().as_str() == "
" { + return TraceAction::BlockWithFullBytecode + } + TraceAction::Block + } /// The output from tracing a `CodeBlock`'s bytecode. fn emit_bytecode_trace(&self, msg: &str); /// The output from entering a `CallFrame`. @@ -19,8 +24,19 @@ pub trait Tracer { fn emit_instruction_trace(&self, msg: &str); /// Trace output from exiting a `CallFrame`. fn emit_call_frame_exit_trace(&self, msg: &str); - /// Tracer name - fn name(&self) -> &str; +} + +/// `TraceAction` Determines the action that should occur +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub enum TraceAction { + /// No trace + None = 0, + /// Traces the frames code block + Block, + /// Partial codeblock with bytecode + BlockWithBytecode, + /// Full trace with bytecode + BlockWithFullBytecode, } #[derive(Debug)] @@ -42,134 +58,52 @@ impl Tracer for ActiveTracer { fn emit_call_frame_exit_trace(&self, msg: &str) { println!("{msg}"); } - - fn name(&self) -> &str { - "Default Active Trace" - } -} - -pub(crate) struct DefaultTracer; - -impl Tracer for DefaultTracer { - fn emit_bytecode_trace(&self, _: &str) {} - - fn emit_call_frame_entrance_trace(&self, _: &str) {} - - fn emit_instruction_trace(&self, _: &str) {} - - fn emit_call_frame_exit_trace(&self, _: &str) {} - - fn name(&self) -> &str { - "Default Empty Trace" - } -} - -bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - pub(crate) struct TraceOptions: u8 { - // Whether the trace is a full trace. - const FULL_TRACE = 0b0000_0001; - } } impl Default for VmTrace { fn default() -> Self { Self { - options: Cell::new(TraceOptions::empty()), - tracer: Box::new(DefaultTracer), + tracers: Vec::default(), } } } /// `VmTrace` is a boa spcific structure for running Boa's Virtual Machine trace. /// -/// The struct provides options for a user to set customized actions for handling -/// messages output during the trace. -/// -/// Currently, the trace supports setting two different actions: -/// - `compiled_action` -/// - `trace_action` +/// It holds registered `Tracer` implementations and actions messages depending on +/// those implementations. /// /// About the actions /// /// After the Global callframe is initially provided. It searches /// for all possible compiled output pub struct VmTrace { - options: Cell, - tracer: Box, + tracers: Vec>, } // ==== Public API ==== impl VmTrace { - #[must_use] - /// Create a partial `VmTrace`. - pub fn partial() -> Self { - Self { - options: Cell::new(TraceOptions::empty()), - tracer: Box::new(ActiveTracer), - } - } - /// Method for adding a compiled action on initialization. pub fn set_tracer(&mut self, tracer: Box) { - self.tracer = tracer; - } - - /// Sets the current `Tracer` of `VmTrace`. - pub fn activate_trace(&mut self) { - self.options.set(TraceOptions::FULL_TRACE); - self.tracer = Box::new(ActiveTracer); + self.tracers.push(tracer); } - /// Sets `VmTrace`s tracer to an active tracer. - pub fn activate_partial_trace(&mut self) { - self.tracer = Box::new(ActiveTracer); + /// Returns whether there is an active trace request. + pub fn should_trace(&self, frame: &CallFrame) -> bool { + self.trace_action(frame) != TraceAction::None } -} -// ==== Internal VmTrace methods ==== - -impl VmTrace { - /// Returns if Trace type is a complete trace. - pub(crate) fn is_full_trace(&self) -> bool { - self.options.get().contains(TraceOptions::FULL_TRACE) + pub(crate) fn trace_action(&self, frame: &CallFrame) -> TraceAction { + (&self.tracers).into_iter().fold(TraceAction::None, |a, b| a.max(b.should_trace(frame))) } - /// Returns how the trace should behave with the given context. - pub(crate) fn should_trace(&self, vm: &Vm) -> TraceAction { - // Check if is no trace or the block is not traceable. - if !self.is_full_trace() && !vm.frame().code_block().traceable() { - return TraceAction::None; - // Check if the block is a full trace. - } else if self.is_full_trace() { - // The bytecode should only be traced on the first callframe. - if vm.frames.len() == 1 { - return TraceAction::FullWithBytecode; - } - return TraceAction::Full; - // If the block was previously traced, we should not emit the bytecode again. - } else if vm.frame().code_block().was_traced() { - return TraceAction::Block; - } - TraceAction::BlockWithBytecode + /// Sets the current `Tracer` of `VmTrace`. + pub fn activate_trace(&mut self) { + self.tracers.push(Box::new(ActiveTracer)); } } -#[derive(Debug, Eq, PartialEq)] -pub(crate) enum TraceAction { - // No trace - None = 0, - // Full trace - Full, - // Full trace with bytecode - FullWithBytecode, - // Partial codeblock trace - Block, - // Partial codeblock with bytecode - BlockWithBytecode, -} - // ==== Trace Event/Action Methods ==== impl VmTrace { @@ -181,12 +115,12 @@ impl VmTrace { /// Trace the current `CallFrame` according to current state pub(crate) fn trace_call_frame(&self, vm: &Vm) { - let action = self.should_trace(vm); + let action = self.trace_action(vm.frame()); match action { - TraceAction::Full | TraceAction::Block => { + TraceAction::Block => { self.call_frame_header(vm); } - TraceAction::FullWithBytecode => { + TraceAction::BlockWithFullBytecode => { self.trace_compiled_bytecode(vm); self.call_frame_header(vm); } @@ -209,7 +143,10 @@ impl VmTrace { "{msg:-^width$}", width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 ); - self.tracer.emit_call_frame_entrance_trace(&frame_header); + + for t in &self.tracers { + t.emit_call_frame_entrance_trace(&frame_header); + } if vm.frames.len() == 1 { let column_headers = format!( @@ -222,7 +159,9 @@ impl VmTrace { OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH, ); - self.tracer.emit_call_frame_entrance_trace(&column_headers); + for t in &self.tracers { + t.emit_call_frame_entrance_trace(&column_headers); + } } } @@ -238,21 +177,24 @@ impl VmTrace { } } - self.tracer.emit_bytecode_trace(&active_block.to_string()); + for t in &self.tracers { + t.emit_bytecode_trace(&active_block.to_string()); + } active_block.set_frame_traced(true); } } /// Searches and traces for only current frame's `CodeBlock`. pub(crate) fn trace_current_bytecode(&self, vm: &Vm) { - self.tracer - .emit_bytecode_trace(&vm.frame().code_block().to_string()); + for t in &self.tracers { + t.emit_bytecode_trace(&vm.frame().code_block().to_string()); + } vm.frame().code_block().set_frame_traced(true); } /// Emits an exit message for the current `CallFrame`. pub(crate) fn trace_frame_end(&self, vm: &Vm, return_msg: &str) { - if self.should_trace(vm) != TraceAction::None { + if self.trace_action(vm.frame()) != TraceAction::None { let msg = format!( " Call Frame -- ", vm.frame().code_block().name.to_std_string_escaped() @@ -262,7 +204,9 @@ impl VmTrace { width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 ); - self.tracer.emit_call_frame_exit_trace(&frame_footer); + for t in &self.tracers { + t.emit_call_frame_exit_trace(&frame_footer); + } } } @@ -283,12 +227,14 @@ impl VmTrace { OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH, ); - self.tracer.emit_instruction_trace(&instruction_trace); + for t in &self.tracers { + t.emit_instruction_trace(&instruction_trace); + } } } impl fmt::Debug for VmTrace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.tracer.name()) + write!(f, "{:?}", self.tracers) } } diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index a177f8f4e6a..73a54bbefa1 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -5,6 +5,7 @@ use boa_engine::{Context, Source, vm::trace::Tracer}; use getrandom as _; use wasm_bindgen::prelude::*; +use std::fmt; #[wasm_bindgen(start)] fn main_js() { @@ -159,6 +160,12 @@ pub(crate) struct WasmTracer { trace_handler: Option, } +impl fmt::Debug for WasmTracer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "WasmTracer") + } +} + impl WasmTracer { fn set_compiled_handler(&mut self, compiled_handler: Box) { self.compiled_handler = Some(compiled_handler); @@ -193,8 +200,4 @@ impl Tracer for WasmTracer { action(msg); } } - - fn name(&self) -> &str { - "WASM Debugger Trace" - } } From b20bb5b3f7574c8632a6198d4d0670d7ecbfaa03 Mon Sep 17 00:00:00 2001 From: nekevss Date: Fri, 19 Jan 2024 19:26:03 -0500 Subject: [PATCH 09/11] cargo fmt and clippy --- cli/src/debug/function.rs | 9 ++++++--- core/engine/src/vm/mod.rs | 2 +- core/engine/src/vm/trace.rs | 21 +++++++++------------ ffi/wasm/src/lib.rs | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cli/src/debug/function.rs b/cli/src/debug/function.rs index ea97b61b665..9d932bd04a0 100644 --- a/cli/src/debug/function.rs +++ b/cli/src/debug/function.rs @@ -2,7 +2,10 @@ use boa_engine::{ builtins::function::OrdinaryFunction, js_string, object::ObjectInitializer, - vm::{flowgraph::{Direction, Graph}, trace::{Tracer, TraceAction}}, + vm::{ + flowgraph::{Direction, Graph}, + trace::{TraceAction, Tracer}, + }, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, }; @@ -130,9 +133,9 @@ impl Tracer for FunctionTracer { fn should_trace(&self, frame: &boa_engine::vm::CallFrame) -> TraceAction { if frame.code_block().traceable() { if frame.code_block().was_traced() { - return TraceAction::Block + return TraceAction::Block; } - return TraceAction::BlockWithBytecode + return TraceAction::BlockWithBytecode; } TraceAction::None } diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 729dba46db9..0f10581e9f2 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -397,7 +397,7 @@ impl Context { } #[cfg(feature = "trace")] - let result = if self.vm.trace.should_trace(&self.vm.frame()) { + let result = if self.vm.trace.should_trace(self.vm.frame()) { self.trace_execute_instruction(f) } else { self.execute_instruction(f) diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 2b20159ec1b..20ba1e65313 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use std::fmt; -use super::{Constant, Vm, CallFrame}; +use super::{CallFrame, Constant, Vm}; // TODO: Build out further, maybe provide more element visiblity and events/outputs /// The `Tracer` trait is a customizable trait that can be provided to `Boa` @@ -12,7 +12,7 @@ pub trait Tracer: fmt::Debug { /// Whether the current call frame should trace. fn should_trace(&self, frame: &CallFrame) -> TraceAction { if frame.code_block.name().to_std_string_escaped().as_str() == "
" { - return TraceAction::BlockWithFullBytecode + return TraceAction::BlockWithFullBytecode; } TraceAction::Block } @@ -60,14 +60,6 @@ impl Tracer for ActiveTracer { } } -impl Default for VmTrace { - fn default() -> Self { - Self { - tracers: Vec::default(), - } - } -} - /// `VmTrace` is a boa spcific structure for running Boa's Virtual Machine trace. /// /// It holds registered `Tracer` implementations and actions messages depending on @@ -77,6 +69,7 @@ impl Default for VmTrace { /// /// After the Global callframe is initially provided. It searches /// for all possible compiled output +#[derive(Default)] pub struct VmTrace { tracers: Vec>, } @@ -90,12 +83,16 @@ impl VmTrace { } /// Returns whether there is an active trace request. - pub fn should_trace(&self, frame: &CallFrame) -> bool { + #[must_use] + pub(crate) fn should_trace(&self, frame: &CallFrame) -> bool { self.trace_action(frame) != TraceAction::None } + /// Returns the folded `TraceAction` of the current `Tracer`s pub(crate) fn trace_action(&self, frame: &CallFrame) -> TraceAction { - (&self.tracers).into_iter().fold(TraceAction::None, |a, b| a.max(b.should_trace(frame))) + self.tracers + .iter() + .fold(TraceAction::None, |a, b| a.max(b.should_trace(frame))) } /// Sets the current `Tracer` of `VmTrace`. diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index 73a54bbefa1..28d5a83d176 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -4,8 +4,8 @@ use boa_engine::{Context, Source, vm::trace::Tracer}; use getrandom as _; -use wasm_bindgen::prelude::*; use std::fmt; +use wasm_bindgen::prelude::*; #[wasm_bindgen(start)] fn main_js() { From 1b95f81d4d4f262514b3aa8fda6d85d6b2fc3c22 Mon Sep 17 00:00:00 2001 From: nekevss Date: Sun, 4 Feb 2024 09:19:36 -0500 Subject: [PATCH 10/11] cargo fmt --- ffi/wasm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ffi/wasm/src/lib.rs b/ffi/wasm/src/lib.rs index 28d5a83d176..d7a1bcea3a5 100644 --- a/ffi/wasm/src/lib.rs +++ b/ffi/wasm/src/lib.rs @@ -2,7 +2,7 @@ #![cfg_attr(not(test), forbid(clippy::unwrap_used))] #![allow(unused_crate_dependencies)] -use boa_engine::{Context, Source, vm::trace::Tracer}; +use boa_engine::{vm::trace::Tracer, Context, Source}; use getrandom as _; use std::fmt; use wasm_bindgen::prelude::*; From 94045e1f138b655b1a82feca5136581b84dfca71 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Fri, 19 Apr 2024 23:57:02 -0400 Subject: [PATCH 11/11] Remove comment and fix duplicate main trace --- core/engine/src/context/mod.rs | 1 - core/engine/src/vm/code_block.rs | 2 +- core/engine/src/vm/trace.rs | 17 ++++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/core/engine/src/context/mod.rs b/core/engine/src/context/mod.rs index 2109ef29c78..486711e0f9d 100644 --- a/core/engine/src/context/mod.rs +++ b/core/engine/src/context/mod.rs @@ -459,7 +459,6 @@ impl Context { #[cfg(feature = "trace")] /// Sets custom handling of trace messages. pub fn set_tracer_implementation(&mut self, tracer: Box) { - // self.init_trace(); self.vm.trace.set_tracer(tracer); } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index d74b2c93bfa..9865589ef3b 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -71,7 +71,7 @@ bitflags! { #[cfg(feature = "trace")] const TRACEABLE = 0b0100_0000_0000_0000; - /// Stores whether the `CodeBlock` been traced. + /// Stores whether the `CodeBlock` has been traced. #[cfg(feature = "trace")] const WAS_TRACED = 0b1000_0000_0000_0000; } diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 20ba1e65313..441e60a4f17 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -12,6 +12,10 @@ pub trait Tracer: fmt::Debug { /// Whether the current call frame should trace. fn should_trace(&self, frame: &CallFrame) -> TraceAction { if frame.code_block.name().to_std_string_escaped().as_str() == "
" { + if frame.code_block().was_traced() { + return TraceAction::Block; + } + frame.code_block().set_frame_traced(true); return TraceAction::BlockWithFullBytecode; } TraceAction::Block @@ -35,7 +39,7 @@ pub enum TraceAction { Block, /// Partial codeblock with bytecode BlockWithBytecode, - /// Full trace with bytecode + /// Full trace with the compiled bytecode BlockWithFullBytecode, } @@ -63,12 +67,7 @@ impl Tracer for ActiveTracer { /// `VmTrace` is a boa spcific structure for running Boa's Virtual Machine trace. /// /// It holds registered `Tracer` implementations and actions messages depending on -/// those implementations. -/// -/// About the actions -/// -/// After the Global callframe is initially provided. It searches -/// for all possible compiled output +/// the `should_trace` method of the `Tracers`. #[derive(Default)] pub struct VmTrace { tracers: Vec>, @@ -95,7 +94,7 @@ impl VmTrace { .fold(TraceAction::None, |a, b| a.max(b.should_trace(frame))) } - /// Sets the current `Tracer` of `VmTrace`. + /// Adds Boa's default implementation of `Tracer` onto `VmTrace`'s current traces. pub fn activate_trace(&mut self) { self.tracers.push(Box::new(ActiveTracer)); } @@ -162,7 +161,7 @@ impl VmTrace { } } - /// Searches traces all of the current `CallFrame`'s available `CodeBlock`s. + /// Searches all of the current `CallFrame`'s available `CodeBlock`s. pub(crate) fn trace_compiled_bytecode(&self, vm: &Vm) { let mut queue = VecDeque::new(); queue.push_back(vm.frame().code_block.clone());