From 01e41cd03fa72e78cc7a00ec3bcf4e67e5ca357d Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Sat, 7 Mar 2026 12:19:32 -0600 Subject: [PATCH 1/6] 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!( + "{: Date: Wed, 2 Sep 2026 22:10:47 -0500 Subject: [PATCH 2/6] Attempt at being a bit more granular --- core/engine/src/vm/code_block.rs | 577 +------------- core/engine/src/vm/mod.rs | 44 +- core/engine/src/vm/opcode/mod.rs | 18 +- core/engine/src/vm/operands.rs | 1198 ++++++++++++++++++++++++++++++ core/engine/src/vm/trace.rs | 181 ++++- 5 files changed, 1401 insertions(+), 617 deletions(-) create mode 100644 core/engine/src/vm/operands.rs diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 77e8aea59f1..b9a91bf8d7c 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -13,13 +13,13 @@ use crate::{ use bitflags::bitflags; use boa_ast::scope::{BindingLocator, Scope}; use boa_gc::{Finalize, Gc, Trace, empty_trace}; -use itertools::Itertools; use std::{cell::Cell, fmt::Display, fmt::Write as _}; use thin_vec::ThinVec; use super::{ InlineCache, - opcode::{Address, ByteCode, Instruction, InstructionIterator}, + opcode::{Address, ByteCode, InstructionIterator}, + operands::Operands, source_info::{SourceInfo, SourceMap, SourcePath}, }; @@ -330,577 +330,6 @@ impl CodeBlock { } } -/// ---- `CodeBlock` private API ---- -impl CodeBlock { - /// Get the operands after the `Opcode` pointed to by `pc` as a `String`. - /// Modifies the `pc` to point to the next instruction. - /// - /// Returns an empty `String` if no operands are present. - pub(crate) fn instruction_operands(&self, instruction: &Instruction) -> String { - match instruction { - Instruction::SetRegisterFromAccumulator { dst } - | Instruction::PopIntoRegister { dst } - | Instruction::PushZero { dst } - | Instruction::PushOne { dst } - | Instruction::PushNan { dst } - | Instruction::PushPositiveInfinity { dst } - | Instruction::PushNegativeInfinity { dst } - | Instruction::PushNull { dst } - | Instruction::PushTrue { dst } - | Instruction::PushFalse { dst } - | Instruction::PushUndefined { dst } - | Instruction::Exception { dst } - | Instruction::This { dst } - | Instruction::NewTarget { dst } - | Instruction::ImportMeta { dst } - | Instruction::CreateMappedArgumentsObject { dst } - | Instruction::CreateUnmappedArgumentsObject { dst } - | Instruction::RestParameterInit { dst } - | Instruction::PushNewArray { dst } => format!("dst:{dst}"), - Instruction::Add { lhs, rhs, dst } - | Instruction::Sub { lhs, rhs, dst } - | Instruction::Div { lhs, rhs, dst } - | Instruction::Mul { lhs, rhs, dst } - | Instruction::Mod { lhs, rhs, dst } - | Instruction::Pow { lhs, rhs, dst } - | Instruction::ShiftRight { lhs, rhs, dst } - | Instruction::ShiftLeft { lhs, rhs, dst } - | Instruction::UnsignedShiftRight { lhs, rhs, dst } - | Instruction::BitOr { lhs, rhs, dst } - | Instruction::BitAnd { lhs, rhs, dst } - | Instruction::BitXor { lhs, rhs, dst } - | Instruction::In { lhs, rhs, dst } - | Instruction::Eq { lhs, rhs, dst } - | Instruction::StrictEq { lhs, rhs, dst } - | Instruction::NotEq { lhs, rhs, dst } - | Instruction::StrictNotEq { lhs, rhs, dst } - | Instruction::GreaterThan { lhs, rhs, dst } - | Instruction::GreaterThanOrEq { lhs, rhs, dst } - | Instruction::LessThan { lhs, rhs, dst } - | Instruction::LessThanOrEq { lhs, rhs, dst } - | Instruction::InstanceOf { lhs, rhs, dst } => { - format!("lhs:{lhs}, rhs:{rhs}, dst:{dst}") - } - Instruction::InPrivate { dst, index, rhs } => { - format!("rhs:{rhs}, index:{index}, dst:{dst}") - } - Instruction::Inc { src, dst } - | Instruction::Dec { src, dst } - | Instruction::Move { src, dst } - | Instruction::ToPropertyKey { src, dst } => { - format!("src:{src}, dst:{dst}") - } - Instruction::SetFunctionName { - function, - name, - prefix, - } => { - format!( - "function:{function}, name:{name}, prefix:{}", - match u32::from(*prefix) { - 0 => "prefix:", - 1 => "prefix: get", - 2 => "prefix: set", - _ => unreachable!(), - } - ) - } - Instruction::PushInt8 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::PushInt16 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::PushInt32 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::PushFloat { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::PushDouble { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::PushLiteral { index, dst } - | Instruction::ThisForObjectEnvironmentName { index, dst } - | Instruction::GetFunction { index, dst } - | Instruction::HasRestrictedGlobalProperty { index, dst } - | Instruction::CanDeclareGlobalFunction { index, dst } - | Instruction::CanDeclareGlobalVar { index, dst } - | Instruction::GetArgument { index, dst } => { - format!("index:{index}, dst:{dst}") - } - Instruction::ThrowNewTypeError { message } - | Instruction::ThrowNewSyntaxError { message } - | Instruction::ThrowNewReferenceError { message } => format!("message:{message}"), - Instruction::PushRegexp { - pattern_index, - flags_index, - dst, - } => { - format!("pattern:{pattern_index}, flags:{flags_index}, dst:{dst}") - } - Instruction::Jump { address } => format!("address:{address}"), - Instruction::JumpIfTrue { address, value } - | Instruction::JumpIfFalse { address, value } - | Instruction::JumpIfNotUndefined { address, value } - | Instruction::JumpIfNullOrUndefined { address, value } - | Instruction::LogicalAnd { address, value } - | Instruction::LogicalOr { address, value } - | Instruction::Coalesce { address, value } => { - format!("value:{value}, address:{address}") - } - Instruction::JumpIfNotLessThan { address, lhs, rhs } - | Instruction::JumpIfNotLessThanOrEqual { address, lhs, rhs } - | Instruction::JumpIfNotGreaterThan { address, lhs, rhs } - | Instruction::JumpIfNotGreaterThanOrEqual { address, lhs, rhs } - | Instruction::JumpIfNotEqual { address, lhs, rhs } => { - format!("lhs:{lhs}, rhs:{rhs}, address:{address}") - } - Instruction::Case { - address, - value, - condition, - } => { - format!("value:{value}, condition:{condition}, address:{address}") - } - Instruction::CallEval { - argument_count, - scope_index, - } => { - format!("argument_count:{argument_count}, scope_index:{scope_index}") - } - Instruction::CallEvalSpread { scope_index } - | Instruction::PushScope { scope_index } => { - format!("scope_index:{scope_index}") - } - Instruction::Call { argument_count } - | Instruction::New { argument_count } - | Instruction::SuperCall { argument_count } => { - format!("argument_count:{argument_count}") - } - Instruction::DefVar { binding_index } | Instruction::GetLocator { binding_index } => { - format!("binding_index:{binding_index}") - } - Instruction::DefInitVar { src, binding_index } - | Instruction::PutLexicalValue { src, binding_index } - | Instruction::SetName { src, binding_index } => { - format!("src:{src}, binding_index:{binding_index}") - } - Instruction::GetName { dst, binding_index } - | Instruction::GetNameAndLocator { dst, binding_index } - | Instruction::GetNameOrUndefined { dst, binding_index } - | Instruction::DeleteName { dst, binding_index } => { - format!("dst:{dst}, binding_index:{binding_index}") - } - Instruction::GetNameGlobal { - dst, - binding_index, - ic_index, - } => { - format!("dst:{dst}, binding_index:{binding_index}, ic_index:{ic_index}") - } - Instruction::DefineOwnPropertyByName { - object, - value, - name_index, - } - | Instruction::SetPropertyGetterByName { - object, - value, - name_index, - } - | Instruction::SetPropertySetterByName { - object, - value, - name_index, - } - | Instruction::DefinePrivateField { - object, - value, - name_index, - } - | Instruction::SetPrivateMethod { - object, - value, - name_index, - } - | Instruction::SetPrivateSetter { - object, - value, - name_index, - } - | Instruction::SetPrivateGetter { - object, - value, - name_index, - } - | Instruction::PushClassPrivateGetter { - object, - value, - name_index, - } - | Instruction::PushClassPrivateSetter { - object, - value, - name_index, - } - | Instruction::DefineClassStaticMethodByName { - object, - value, - name_index, - } - | Instruction::DefineClassMethodByName { - object, - value, - name_index, - } - | Instruction::DefineClassStaticGetterByName { - object, - value, - name_index, - } - | Instruction::DefineClassGetterByName { - object, - value, - name_index, - } - | Instruction::DefineClassStaticSetterByName { - object, - value, - name_index, - } - | Instruction::DefineClassSetterByName { - object, - value, - name_index, - } - | Instruction::SetPrivateField { - object, - value, - name_index, - } - | Instruction::PushClassFieldPrivate { - object, - value, - name_index, - } => { - format!("object:{object}, value:{value}, name_index:{name_index}") - } - Instruction::GetPrivateField { - dst, - object, - name_index, - } => { - format!("dst:{dst}, object:{object}, name_index:{name_index}") - } - Instruction::PushClassPrivateMethod { - object, - proto, - value, - name_index, - } => { - format!("object:{object}, proto:{proto}, value:{value}, name_index:{name_index}") - } - Instruction::ThrowMutateImmutable { index } => { - format!("index:{index}") - } - Instruction::DeletePropertyByName { object, name_index } - | Instruction::GetMethod { object, name_index } => { - format!("object:{object}, name_index:{name_index}") - } - Instruction::GetLengthProperty { - dst, - value, - ic_index, - } - | Instruction::GetPropertyByName { - dst, - value, - ic_index, - } => { - let ic = &self.ic[u32::from(*ic_index) as usize]; - format!("dst:{dst}, value:{value}, ic:{ic}",) - } - Instruction::GetPropertyByNameWithThis { - dst, - receiver, - value, - ic_index, - } => { - let ic = &self.ic[u32::from(*ic_index) as usize]; - format!("dst:{dst}, receiver:{receiver}, value:{value}, ic:{ic}",) - } - Instruction::SetPropertyByName { - value, - object, - ic_index, - } => { - let ic = &self.ic[u32::from(*ic_index) as usize]; - format!("object:{object}, value:{value}, ic:{ic}",) - } - Instruction::SetPropertyByNameWithThis { - value, - receiver, - object, - ic_index, - } => { - let ic = &self.ic[u32::from(*ic_index) as usize]; - format!("object:{object}, receiver:{receiver}, value:{value}, ic:{ic}") - } - Instruction::GetPropertyByValue { - dst, - key, - receiver, - object, - } - | Instruction::GetPropertyByValuePush { - dst, - key, - receiver, - object, - } => { - format!("dst:{dst}, object:{object}, receiver:{receiver}, key:{key}") - } - Instruction::SetPropertyByValue { - value, - key, - receiver, - object, - } => { - format!("object:{object}, receiver:{receiver}, key:{key}, value:{value}") - } - Instruction::DefineOwnPropertyByValue { value, key, object } - | Instruction::DefineClassStaticMethodByValue { value, key, object } - | Instruction::DefineClassMethodByValue { value, key, object } - | Instruction::SetPropertyGetterByValue { value, key, object } - | Instruction::DefineClassStaticGetterByValue { value, key, object } - | Instruction::DefineClassGetterByValue { value, key, object } - | Instruction::SetPropertySetterByValue { value, key, object } - | Instruction::DefineClassStaticSetterByValue { value, key, object } - | Instruction::DefineClassSetterByValue { value, key, object } => { - format!("object:{object}, key:{key}, value:{value}") - } - Instruction::DeletePropertyByValue { key, object } => { - format!("object:{object}, key:{key}") - } - Instruction::CreateIteratorResult { value, done } => { - format!("value:{value}, done:{done}") - } - Instruction::PushClassPrototype { - dst, - class, - superclass, - } => { - format!("dst:{dst}, class:{class}, superclass:{superclass}") - } - Instruction::SetClassPrototype { - dst, - prototype, - class, - } => { - format!("dst:{dst}, prototype:{prototype}, class:{class}") - } - Instruction::SetHomeObject { function, home } => { - format!("function:{function}, home:{home}") - } - Instruction::GetHomeObject { function } => { - format!("function:{function}") - } - Instruction::SetPrototype { object, prototype } => { - format!("object:{object}, prototype:{prototype}") - } - Instruction::GetPrototype { object } => { - format!("object:{object}") - } - Instruction::PushValueToArray { value, array } => { - format!("value:{value}, array:{array}") - } - Instruction::PushElisionToArray { array } - | Instruction::PushIteratorToArray { array } => { - format!("array:{array}") - } - Instruction::TypeOf { value } - | Instruction::LogicalNot { value } - | Instruction::Pos { value } - | Instruction::Neg { value } - | Instruction::IsObject { value } - | Instruction::BindThisValue { value } - | Instruction::BitNot { value } => { - format!("value:{value}") - } - Instruction::ImportCall { specifier, options } => { - format!("specifier:{specifier}, options:{options}") - } - Instruction::PushClassField { - object, - name, - value, - is_anonymous_function, - } => { - format!( - "object:{object}, value:{value}, name:{name}, is_anonymous_function:{is_anonymous_function}" - ) - } - Instruction::MaybeException { - has_exception, - exception, - } => { - format!("has_exception:{has_exception}, exception:{exception}") - } - Instruction::SetAccumulator { src } - | Instruction::PushFromRegister { src } - | Instruction::Throw { src } - | Instruction::SetNameByLocator { src } - | Instruction::PushObjectEnvironment { src } - | Instruction::CreateForInIterator { src } - | Instruction::GetIterator { src } - | Instruction::GetAsyncIterator { src } - | Instruction::ValueNotNullOrUndefined { src } - | Instruction::GeneratorYield { src } - | Instruction::AsyncGeneratorYield { src } - | Instruction::Await { src } => { - format!("src:{src}") - } - Instruction::IteratorPush { iterator, next } - | Instruction::IteratorPop { iterator, next } => { - format!("iterator:{iterator}, next:{next}") - } - Instruction::IteratorUpdateResult { result } => { - format!("result:{result}") - } - Instruction::IteratorDone { dst } - | Instruction::IteratorValue { dst } - | Instruction::IteratorResult { dst } - | Instruction::IteratorToArray { dst } - | Instruction::IteratorStackEmpty { dst } - | Instruction::PushEmptyObject { dst } => { - format!("dst:{dst}") - } - Instruction::IteratorFinishAsyncNext { resume_kind, value } => { - format!("resume_kind:{resume_kind}, value:{value}") - } - Instruction::IteratorReturn { value, called } => { - format!("value:{value}, called:{called}") - } - Instruction::CreateGlobalFunctionBinding { - src, - configurable, - name_index, - } => { - format!("src:{src}, configurable:{configurable}, name_index:{name_index}") - } - Instruction::CreateGlobalVarBinding { - configurable, - name_index, - } => { - format!("configurable:{configurable}, name_index:{name_index}") - } - Instruction::PushPrivateEnvironment { - class, - name_indices, - } => { - format!("class:{class}, names:{name_indices:?}") - } - Instruction::TemplateLookup { address, site, dst } => { - format!("address:{address}, site:{site}, dst:{dst}") - } - Instruction::JumpTable { index, addresses } => { - format!( - "index:{index}, jump_table:({})", - addresses.iter().format(", ") - ) - } - Instruction::ConcatToString { dst, values } => { - format!("dst:{dst}, values:{values:?}") - } - Instruction::CopyDataProperties { - object, - source, - excluded_keys, - } => { - format!("object:{object}, source:{source}, excluded_keys:{excluded_keys:?}") - } - Instruction::TemplateCreate { site, dst, values } => { - format!("site:{site}, dst:{dst}, values:{values:?}") - } - Instruction::GetFunctionObject { function_object } => { - format!("function_object:{function_object}") - } - Instruction::Pop - | Instruction::DeleteSuperThrow - | Instruction::ReThrow - | Instruction::CheckReturn - | Instruction::Return - | Instruction::AsyncGeneratorClose - | Instruction::CreatePromiseCapability - | Instruction::PopEnvironment - | Instruction::IncrementLoopIteration - | Instruction::IteratorNext - | Instruction::SuperCallDerived - | Instruction::CallSpread - | Instruction::NewSpread - | Instruction::SuperCallSpread - | Instruction::PopPrivateEnvironment - | Instruction::Generator - | Instruction::AsyncGenerator => String::new(), - Instruction::Reserved1 - | Instruction::Reserved2 - | Instruction::Reserved3 - | Instruction::Reserved4 - | Instruction::Reserved5 - | Instruction::Reserved6 - | Instruction::Reserved7 - | Instruction::Reserved8 - | Instruction::Reserved9 - | Instruction::Reserved10 - | Instruction::Reserved11 - | Instruction::Reserved12 - | Instruction::Reserved13 - | Instruction::Reserved14 - | Instruction::Reserved15 - | Instruction::Reserved16 - | Instruction::Reserved17 - | Instruction::Reserved18 - | Instruction::Reserved19 - | Instruction::Reserved20 - | Instruction::Reserved21 - | Instruction::Reserved22 - | Instruction::Reserved23 - | Instruction::Reserved24 - | Instruction::Reserved25 - | Instruction::Reserved26 - | Instruction::Reserved27 - | Instruction::Reserved28 - | Instruction::Reserved29 - | Instruction::Reserved30 - | Instruction::Reserved31 - | Instruction::Reserved32 - | Instruction::Reserved33 - | Instruction::Reserved34 - | Instruction::Reserved35 - | Instruction::Reserved36 - | Instruction::Reserved37 - | Instruction::Reserved38 - | Instruction::Reserved39 - | Instruction::Reserved40 - | Instruction::Reserved41 - | Instruction::Reserved42 - | Instruction::Reserved43 - | Instruction::Reserved44 - | Instruction::Reserved45 - | Instruction::Reserved46 - | Instruction::Reserved47 - | Instruction::Reserved48 - | Instruction::Reserved49 - | Instruction::Reserved50 - | Instruction::Reserved51 - | Instruction::Reserved52 - | Instruction::Reserved53 - | Instruction::Reserved54 => unreachable!("Reserved opcodes are unreachable"), - } - } -} - impl Display for CodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = self.name(); @@ -916,7 +345,7 @@ impl Display for CodeBlock { let mut iterator = InstructionIterator::new(&self.bytecode); while let Some((instruction_start_pc, opcode, instruction)) = iterator.next() { let opcode = opcode.as_str(); - let operands = self.instruction_operands(&instruction); + let operands = Operands::from_instruction(&instruction); let pc = iterator.pc(); let handler = if let Some((i, handler)) = self.find_handler(instruction_start_pc as u32) { diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index eb4faa5638d..9a1a97de146 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -56,6 +56,8 @@ pub(crate) mod opcode; pub(crate) mod shadow_stack; pub(crate) mod source_info; +mod operands; + #[cfg(feature = "trace")] mod trace; @@ -281,30 +283,6 @@ impl Stack { let index = self.stack.len() - existing_argument_count; self.stack.splice(index..index, arguments.iter().cloned()); } - - #[cfg(feature = "trace")] - /// Display the stack trace of the current frame. - fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String { - let mut string = String::from("[ "); - for (i, (j, value)) in self.stack.iter().enumerate().rev().enumerate() { - match value { - value if value.is_callable() => string.push_str("[function]"), - value if value.is_object() => string.push_str("[object]"), - value => string.push_str(&value.display().to_string()), - } - - if frame.frame_pointer() == j { - let _ = write!(string, " |{frame_count}|"); - } else if i + 1 != self.stack.len() { - string.push(','); - } - - string.push(' '); - } - - string.push(']'); - string - } } /// Active runnable in the current vm context. @@ -634,18 +612,15 @@ impl Context { where F: FnOnce(&mut Context, Opcode) -> ControlFlow, { - use crate::vm::trace::{OpcodeExecutionMessage, VirtualMachineEvent}; + use crate::vm::operands::Operands; + use crate::vm::trace::{OpcodeExecutionMessage, VirtualMachineEvent, VmStackTrace}; let frame = self.vm.frame(); let (instruction, _) = frame .code_block .bytecode .next_instruction(frame.pc as usize); - let operands = self - .vm - .frame() - .code_block() - .instruction_operands(&instruction); + let operands = Operands::from_instruction(&instruction); match opcode { Opcode::Call @@ -669,19 +644,16 @@ impl Context { let result = self.execute_instruction(f, opcode); let duration = instant.elapsed(); - let stack = self - .vm - .stack - .display_trace(self.vm.frame(), self.vm.frames.len() - 1); + let stack_trace = VmStackTrace::new(&self.vm); self.vm .tracer .emit_event(VirtualMachineEvent::ExecutionTrace( OpcodeExecutionMessage { - opcode: opcode.as_str(), + opcode, duration, operands, - stack, + stack_trace, }, )); diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 0ff9c921832..fe305674602 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -224,12 +224,19 @@ impl std::fmt::Display for Address { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] /// A register operand is a register index used in bytecode instructions. pub(crate) struct RegisterOperand { value: u32, } +impl std::ops::Deref for RegisterOperand { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.value + } +} + impl RegisterOperand { /// Create a new [`RegisterOperand`] from a u32 value. pub(crate) fn new(value: u32) -> Self { @@ -274,12 +281,19 @@ impl std::fmt::Display for RegisterOperand { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] /// A varying operand is a value that can be either a u8, u16 or u32. pub(crate) struct VaryingOperand { value: u32, } +impl std::ops::Deref for VaryingOperand { + type Target = u32; + fn deref(&self) -> &Self::Target { + &self.value + } +} + impl VaryingOperand { /// Create a new [`VaryingOperand`] from a u32 value. pub(crate) fn new(value: u32) -> Self { diff --git a/core/engine/src/vm/operands.rs b/core/engine/src/vm/operands.rs new file mode 100644 index 00000000000..b24cc7825c5 --- /dev/null +++ b/core/engine/src/vm/operands.rs @@ -0,0 +1,1198 @@ +use crate::vm::Instruction; + +/// Available Operands types that Boa's VM uses +#[derive(Clone, Debug, PartialEq)] +pub enum Operands { + None, + Dst { + dst: u32, + }, + LhsRhsDst { + lhs: u32, + rhs: u32, + dst: u32, + }, + RhsIndexDst { + rhs: u32, + index: u32, + dst: u32, + }, + SrcDst { + src: u32, + dst: u32, + }, + SetFunctionName { + function: u32, + name: u32, + prefix: u8, + }, + ValueDst { + value: f64, + dst: u32, + }, + IndexDst { + index: u32, + dst: u32, + }, + Message { + message: u32, + }, + Regexp { + pattern_index: u32, + flags_index: u32, + dst: u32, + }, + Address { + address: u32, + }, + AddressValue { + address: u32, + value: u32, + }, + AddressLhsRhs { + address: u32, + lhs: u32, + rhs: u32, + }, + Case { + address: u32, + value: u32, + condition: u32, + }, + CallEval { + argument_count: u32, + scope_index: u32, + }, + ScopeIndex { + scope_index: u32, + }, + ArgumentCount { + argument_count: u32, + }, + BindingIndex { + binding_index: u32, + }, + SrcBindingIndex { + src: u32, + binding_index: u32, + }, + DstBindingIndex { + dst: u32, + binding_index: u32, + }, + GetNameGlobal { + dst: u32, + binding_index: u32, + ic_index: u32, + }, + ObjectValueName { + object: u32, + value: u32, + name_index: u32, + }, + DstObjectName { + dst: u32, + object: u32, + name_index: u32, + }, + ObjectProtoValueName { + object: u32, + proto: u32, + value: u32, + name_index: u32, + }, + Index { + index: u32, + }, + ObjectName { + object: u32, + name_index: u32, + }, + DstValueIc { + dst: u32, + value: u32, + ic_index: u32, + }, + DstReceiverValueIc { + dst: u32, + receiver: u32, + value: u32, + ic_index: u32, + }, + ObjectValueIc { + object: u32, + value: u32, + ic_index: u32, + }, + ObjectReceiverValueIc { + object: u32, + receiver: u32, + value: u32, + ic_index: u32, + }, + DstKeyReceiverObject { + dst: u32, + key: u32, + receiver: u32, + object: u32, + }, + ObjectReceiverKeyValue { + object: u32, + receiver: u32, + key: u32, + value: u32, + }, + ObjectKeyValue { + object: u32, + key: u32, + value: u32, + }, + ObjectKey { + object: u32, + key: u32, + }, + ValueDone { + value: u32, + done: u32, + }, + DstClassSuperclass { + dst: u32, + class: u32, + superclass: u32, + }, + DstPrototypeClass { + dst: u32, + prototype: u32, + class: u32, + }, + FunctionHome { + function: u32, + home: u32, + }, + Function { + function: u32, + }, + ObjectPrototype { + object: u32, + prototype: u32, + }, + Object { + object: u32, + }, + ValueArray { + value: u32, + array: u32, + }, + Array { + array: u32, + }, + Value { + value: u32, + }, + SpecifierOptions { + specifier: u32, + options: u32, + }, + ClassField { + object: u32, + name: u32, + value: u32, + is_anonymous_function: u32, + }, + MaybeException { + has_exception: u32, + exception: u32, + }, + Src { + src: u32, + }, + IteratorNextReg { + iterator: u32, + next: u32, + }, + Result { + result: u32, + }, + ResumeKindValue { + resume_kind: u32, + value: u32, + }, + ValueCalled { + value: u32, + called: u32, + }, + SrcConfigurableName { + src: u32, + configurable: u32, + name_index: u32, + }, + ConfigurableName { + configurable: bool, + name_index: u32, + }, + ClassNames { + class: u32, + name_indices: Box<[u32]>, + }, + AddressSiteDst { + address: u32, + site: u64, + dst: u32, + }, + JumpTable { + index: u32, + addresses: Box<[u32]>, + }, + DstValues { + dst: u32, + values: Box<[u32]>, + }, + ObjectSourceExcluded { + object: u32, + source: u32, + excluded_keys: Box<[u32]>, + }, + SiteDstValues { + site: u64, + dst: u32, + values: Box<[u32]>, + }, + FunctionObject { + function_object: u32, + }, +} + +impl Operands { + pub fn from_instruction(instruction: &Instruction) -> Self { + match instruction { + Instruction::Pop + | Instruction::DeleteSuperThrow + | Instruction::ReThrow + | Instruction::CheckReturn + | Instruction::Return + | Instruction::AsyncGeneratorClose + | Instruction::CreatePromiseCapability + | Instruction::PopEnvironment + | Instruction::IncrementLoopIteration + | Instruction::IteratorNext + | Instruction::SuperCallDerived + | Instruction::CallSpread + | Instruction::NewSpread + | Instruction::SuperCallSpread + | Instruction::PopPrivateEnvironment + | Instruction::Generator + | Instruction::AsyncGenerator => Operands::None, + + Instruction::SetRegisterFromAccumulator { dst } + | Instruction::PopIntoRegister { dst } + | Instruction::PushZero { dst } + | Instruction::PushOne { dst } + | Instruction::PushNan { dst } + | Instruction::PushPositiveInfinity { dst } + | Instruction::PushNegativeInfinity { dst } + | Instruction::PushNull { dst } + | Instruction::PushTrue { dst } + | Instruction::PushFalse { dst } + | Instruction::PushUndefined { dst } + | Instruction::Exception { dst } + | Instruction::This { dst } + | Instruction::NewTarget { dst } + | Instruction::ImportMeta { dst } + | Instruction::CreateMappedArgumentsObject { dst } + | Instruction::CreateUnmappedArgumentsObject { dst } + | Instruction::RestParameterInit { dst } + | Instruction::PushNewArray { dst } => Operands::Dst { dst: **dst }, + + Instruction::Add { lhs, rhs, dst } + | Instruction::Sub { lhs, rhs, dst } + | Instruction::Div { lhs, rhs, dst } + | Instruction::Mul { lhs, rhs, dst } + | Instruction::Mod { lhs, rhs, dst } + | Instruction::Pow { lhs, rhs, dst } + | Instruction::ShiftRight { lhs, rhs, dst } + | Instruction::ShiftLeft { lhs, rhs, dst } + | Instruction::UnsignedShiftRight { lhs, rhs, dst } + | Instruction::BitOr { lhs, rhs, dst } + | Instruction::BitAnd { lhs, rhs, dst } + | Instruction::BitXor { lhs, rhs, dst } + | Instruction::In { lhs, rhs, dst } + | Instruction::Eq { lhs, rhs, dst } + | Instruction::StrictEq { lhs, rhs, dst } + | Instruction::NotEq { lhs, rhs, dst } + | Instruction::StrictNotEq { lhs, rhs, dst } + | Instruction::GreaterThan { lhs, rhs, dst } + | Instruction::GreaterThanOrEq { lhs, rhs, dst } + | Instruction::LessThan { lhs, rhs, dst } + | Instruction::LessThanOrEq { lhs, rhs, dst } + | Instruction::InstanceOf { lhs, rhs, dst } => Operands::LhsRhsDst { + lhs: **lhs, + rhs: **rhs, + dst: **dst, + }, + + Instruction::InPrivate { dst, index, rhs } => Operands::RhsIndexDst { + rhs: **rhs, + index: **index, + dst: **dst, + }, + + Instruction::Inc { src, dst } + | Instruction::Dec { src, dst } + | Instruction::Move { src, dst } + | Instruction::ToPropertyKey { src, dst } => Operands::SrcDst { + src: u32::from(*src), + dst: **dst, + }, + + Instruction::SetFunctionName { + function, + name, + prefix, + } => Operands::SetFunctionName { + function: **function, + name: **name, + prefix: u32::from(*prefix) as u8, + }, + + Instruction::PushInt8 { value, dst } => Operands::ValueDst { + value: f64::from(*value), + dst: **dst, + }, + Instruction::PushInt16 { value, dst } => Operands::ValueDst { + value: f64::from(*value), + dst: **dst, + }, + Instruction::PushInt32 { value, dst } => Operands::ValueDst { + value: f64::from(*value), + dst: **dst, + }, + Instruction::PushFloat { value, dst } => Operands::ValueDst { + value: f64::from(*value), + dst: **dst, + }, + Instruction::PushDouble { value, dst } => Operands::ValueDst { + value: *value, + dst: **dst, + }, + + Instruction::PushLiteral { index, dst } + | Instruction::ThisForObjectEnvironmentName { index, dst } + | Instruction::GetFunction { index, dst } + | Instruction::HasRestrictedGlobalProperty { index, dst } + | Instruction::CanDeclareGlobalFunction { index, dst } + | Instruction::CanDeclareGlobalVar { index, dst } + | Instruction::GetArgument { index, dst } => Operands::IndexDst { + index: **index, + dst: **dst, + }, + + Instruction::ThrowNewTypeError { message } + | Instruction::ThrowNewSyntaxError { message } + | Instruction::ThrowNewReferenceError { message } => { + Operands::Message { message: **message } + } + + Instruction::PushRegexp { + pattern_index, + flags_index, + dst, + } => Operands::Regexp { + pattern_index: **pattern_index, + flags_index: **flags_index, + dst: **dst, + }, + + Instruction::Jump { address } => Operands::Address { + address: u32::from(*address), + }, + + Instruction::JumpIfTrue { address, value } + | Instruction::JumpIfFalse { address, value } + | Instruction::JumpIfNotUndefined { address, value } + | Instruction::JumpIfNullOrUndefined { address, value } + | Instruction::LogicalAnd { address, value } + | Instruction::LogicalOr { address, value } + | Instruction::Coalesce { address, value } => Operands::AddressValue { + address: u32::from(*address), + value: **value, + }, + + Instruction::JumpIfNotLessThan { address, lhs, rhs } + | Instruction::JumpIfNotLessThanOrEqual { address, lhs, rhs } + | Instruction::JumpIfNotGreaterThan { address, lhs, rhs } + | Instruction::JumpIfNotGreaterThanOrEqual { address, lhs, rhs } + | Instruction::JumpIfNotEqual { address, lhs, rhs } => Operands::AddressLhsRhs { + address: u32::from(*address), + lhs: **lhs, + rhs: **rhs, + }, + + Instruction::Case { + address, + value, + condition, + } => Operands::Case { + address: u32::from(*address), + value: **value, + condition: **condition, + }, + + Instruction::CallEval { + argument_count, + scope_index, + } => Operands::CallEval { + argument_count: **argument_count, + scope_index: **scope_index, + }, + + Instruction::CallEvalSpread { scope_index } + | Instruction::PushScope { scope_index } => Operands::ScopeIndex { + scope_index: **scope_index, + }, + + Instruction::Call { argument_count } + | Instruction::New { argument_count } + | Instruction::SuperCall { argument_count } => Operands::ArgumentCount { + argument_count: **argument_count, + }, + + Instruction::DefVar { binding_index } | Instruction::GetLocator { binding_index } => { + Operands::BindingIndex { + binding_index: **binding_index, + } + } + + Instruction::DefInitVar { src, binding_index } + | Instruction::PutLexicalValue { src, binding_index } + | Instruction::SetName { src, binding_index } => Operands::SrcBindingIndex { + src: u32::from(*src), + binding_index: **binding_index, + }, + + Instruction::GetName { dst, binding_index } + | Instruction::GetNameAndLocator { dst, binding_index } + | Instruction::GetNameOrUndefined { dst, binding_index } + | Instruction::DeleteName { dst, binding_index } => Operands::DstBindingIndex { + dst: **dst, + binding_index: **binding_index, + }, + + Instruction::GetNameGlobal { + dst, + binding_index, + ic_index, + } => Operands::GetNameGlobal { + dst: **dst, + binding_index: **binding_index, + ic_index: **ic_index, + }, + + Instruction::DefineOwnPropertyByName { + object, + value, + name_index, + } + | Instruction::SetPropertyGetterByName { + object, + value, + name_index, + } + | Instruction::SetPropertySetterByName { + object, + value, + name_index, + } + | Instruction::DefinePrivateField { + object, + value, + name_index, + } + | Instruction::SetPrivateMethod { + object, + value, + name_index, + } + | Instruction::SetPrivateSetter { + object, + value, + name_index, + } + | Instruction::SetPrivateGetter { + object, + value, + name_index, + } + | Instruction::PushClassPrivateGetter { + object, + value, + name_index, + } + | Instruction::PushClassPrivateSetter { + object, + value, + name_index, + } + | Instruction::DefineClassStaticMethodByName { + object, + value, + name_index, + } + | Instruction::DefineClassMethodByName { + object, + value, + name_index, + } + | Instruction::DefineClassStaticGetterByName { + object, + value, + name_index, + } + | Instruction::DefineClassGetterByName { + object, + value, + name_index, + } + | Instruction::DefineClassStaticSetterByName { + object, + value, + name_index, + } + | Instruction::DefineClassSetterByName { + object, + value, + name_index, + } + | Instruction::SetPrivateField { + object, + value, + name_index, + } + | Instruction::PushClassFieldPrivate { + object, + value, + name_index, + } => Operands::ObjectValueName { + object: **object, + value: **value, + name_index: **name_index, + }, + Instruction::GetPrivateField { + dst, + object, + name_index, + } => Operands::DstObjectName { + dst: **dst, + object: **object, + name_index: **name_index, + }, + Instruction::PushClassPrivateMethod { + object, + proto, + value, + name_index, + } => Operands::ObjectProtoValueName { + object: **object, + proto: **proto, + value: **value, + name_index: **name_index, + }, + Instruction::ThrowMutateImmutable { index } => Operands::Index { index: **index }, + Instruction::DeletePropertyByName { object, name_index } + | Instruction::GetMethod { object, name_index } => Operands::ObjectName { + object: **object, + name_index: **name_index, + }, + Instruction::GetLengthProperty { + dst, + value, + ic_index, + } + | Instruction::GetPropertyByName { + dst, + value, + ic_index, + } => Operands::DstValueIc { + dst: **dst, + value: **value, + ic_index: **ic_index, + }, + Instruction::GetPropertyByNameWithThis { + dst, + receiver, + value, + ic_index, + } => Operands::DstReceiverValueIc { + dst: **dst, + receiver: **receiver, + value: **value, + ic_index: **ic_index, + }, + Instruction::SetPropertyByName { + value, + object, + ic_index, + } => Operands::ObjectValueIc { + object: **object, + value: **value, + ic_index: **ic_index, + }, + Instruction::SetPropertyByNameWithThis { + value, + receiver, + object, + ic_index, + } => Operands::ObjectReceiverValueIc { + object: **object, + receiver: **receiver, + value: **value, + ic_index: **ic_index, + }, + Instruction::GetPropertyByValue { + dst, + key, + receiver, + object, + } + | Instruction::GetPropertyByValuePush { + dst, + key, + receiver, + object, + } => Operands::DstKeyReceiverObject { + dst: **dst, + key: **key, + receiver: **receiver, + object: **object, + }, + Instruction::SetPropertyByValue { + value, + key, + receiver, + object, + } => Operands::ObjectReceiverKeyValue { + object: **object, + receiver: **receiver, + key: **key, + value: **value, + }, + Instruction::DefineOwnPropertyByValue { value, key, object } + | Instruction::DefineClassStaticMethodByValue { value, key, object } + | Instruction::DefineClassMethodByValue { value, key, object } + | Instruction::SetPropertyGetterByValue { value, key, object } + | Instruction::DefineClassStaticGetterByValue { value, key, object } + | Instruction::DefineClassGetterByValue { value, key, object } + | Instruction::SetPropertySetterByValue { value, key, object } + | Instruction::DefineClassStaticSetterByValue { value, key, object } + | Instruction::DefineClassSetterByValue { value, key, object } => { + Operands::ObjectKeyValue { + object: **object, + key: **key, + value: **value, + } + } + Instruction::DeletePropertyByValue { key, object } => Operands::ObjectKey { + object: **object, + key: **key, + }, + Instruction::CreateIteratorResult { value, done } => Operands::ValueDone { + value: **value, + done: **done, + }, + Instruction::PushClassPrototype { + dst, + class, + superclass, + } => Operands::DstClassSuperclass { + dst: **dst, + class: **class, + superclass: **superclass, + }, + Instruction::SetClassPrototype { + dst, + prototype, + class, + } => Operands::DstPrototypeClass { + dst: u32::from(**dst), + prototype: **prototype, + class: **class, + }, + Instruction::SetHomeObject { function, home } => Operands::FunctionHome { + function: **function, + home: **home, + }, + Instruction::GetHomeObject { function } => Operands::Function { + function: **function, + }, + Instruction::SetPrototype { object, prototype } => Operands::ObjectPrototype { + object: **object, + prototype: **prototype, + }, + Instruction::GetPrototype { object } => Operands::Object { object: **object }, + Instruction::PushValueToArray { value, array } => Operands::ValueArray { + value: **value, + array: **array, + }, + Instruction::PushElisionToArray { array } + | Instruction::PushIteratorToArray { array } => Operands::Array { array: **array }, + Instruction::TypeOf { value } + | Instruction::LogicalNot { value } + | Instruction::Pos { value } + | Instruction::Neg { value } + | Instruction::IsObject { value } + | Instruction::BindThisValue { value } + | Instruction::BitNot { value } => Operands::Value { value: **value }, + Instruction::ImportCall { specifier, options } => Operands::SpecifierOptions { + specifier: **specifier, + options: **options, + }, + Instruction::PushClassField { + object, + name, + value, + is_anonymous_function, + } => Operands::ClassField { + object: **object, + name: **name, + value: **value, + is_anonymous_function: **is_anonymous_function, + }, + Instruction::MaybeException { + has_exception, + exception, + } => Operands::MaybeException { + has_exception: **has_exception, + exception: **exception, + }, + Instruction::SetAccumulator { src } + | Instruction::PushFromRegister { src } + | Instruction::Throw { src } + | Instruction::SetNameByLocator { src } + | Instruction::PushObjectEnvironment { src } + | Instruction::CreateForInIterator { src } + | Instruction::GetIterator { src } + | Instruction::GetAsyncIterator { src } + | Instruction::ValueNotNullOrUndefined { src } + | Instruction::GeneratorYield { src } + | Instruction::AsyncGeneratorYield { src } + | Instruction::Await { src } => Operands::Src { + src: u32::from(*src), + }, + Instruction::IteratorPush { iterator, next } + | Instruction::IteratorPop { iterator, next } => Operands::IteratorNextReg { + iterator: **iterator, + next: **next, + }, + Instruction::IteratorUpdateResult { result } => Operands::Result { result: **result }, + Instruction::IteratorDone { dst } + | Instruction::IteratorValue { dst } + | Instruction::IteratorResult { dst } + | Instruction::IteratorToArray { dst } + | Instruction::IteratorStackEmpty { dst } + | Instruction::PushEmptyObject { dst } => Operands::Dst { dst: **dst }, + Instruction::IteratorFinishAsyncNext { resume_kind, value } => { + Operands::ResumeKindValue { + resume_kind: **resume_kind, + value: **value, + } + } + Instruction::IteratorReturn { value, called } => Operands::ValueCalled { + value: **value, + called: **called, + }, + Instruction::CreateGlobalFunctionBinding { + src, + configurable, + name_index, + } => Operands::SrcConfigurableName { + src: **src, + configurable: **configurable, + name_index: **name_index, + }, + Instruction::CreateGlobalVarBinding { + configurable, + name_index, + } => Operands::ConfigurableName { + configurable: u32::from(*configurable) == 1, + name_index: **name_index, + }, + Instruction::PushPrivateEnvironment { + class, + name_indices, + } => Operands::ClassNames { + class: **class, + name_indices: name_indices + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::TemplateLookup { address, site, dst } => Operands::AddressSiteDst { + address: u32::from(*address), + site: *site, + dst: **dst, + }, + Instruction::JumpTable { index, addresses } => Operands::JumpTable { + index: *index, + addresses: addresses + .iter() + .copied() + .map(u32::from) + .collect::>() + .into_boxed_slice(), + }, + Instruction::ConcatToString { dst, values } => Operands::DstValues { + dst: **dst, + values: values + .iter() + .map(std::ops::Deref::deref) + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::CopyDataProperties { + object, + source, + excluded_keys, + } => Operands::ObjectSourceExcluded { + object: **object, + source: **source, + excluded_keys: excluded_keys + .iter() + .map(std::ops::Deref::deref) + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::TemplateCreate { site, dst, values } => Operands::SiteDstValues { + site: *site, + dst: **dst, + values: values + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::GetFunctionObject { function_object } => Operands::FunctionObject { + function_object: **function_object, + }, + Instruction::Reserved1 + | Instruction::Reserved2 + | Instruction::Reserved3 + | Instruction::Reserved4 + | Instruction::Reserved5 + | Instruction::Reserved6 + | Instruction::Reserved7 + | Instruction::Reserved8 + | Instruction::Reserved9 + | Instruction::Reserved10 + | Instruction::Reserved11 + | Instruction::Reserved12 + | Instruction::Reserved13 + | Instruction::Reserved14 + | Instruction::Reserved15 + | Instruction::Reserved16 + | Instruction::Reserved17 + | Instruction::Reserved18 + | Instruction::Reserved19 + | Instruction::Reserved20 + | Instruction::Reserved21 + | Instruction::Reserved22 + | Instruction::Reserved23 + | Instruction::Reserved24 + | Instruction::Reserved25 + | Instruction::Reserved26 + | Instruction::Reserved27 + | Instruction::Reserved28 + | Instruction::Reserved29 + | Instruction::Reserved30 + | Instruction::Reserved31 + | Instruction::Reserved32 + | Instruction::Reserved33 + | Instruction::Reserved34 + | Instruction::Reserved35 + | Instruction::Reserved36 + | Instruction::Reserved37 + | Instruction::Reserved38 + | Instruction::Reserved39 + | Instruction::Reserved40 + | Instruction::Reserved41 + | Instruction::Reserved42 + | Instruction::Reserved43 + | Instruction::Reserved44 + | Instruction::Reserved45 + | Instruction::Reserved46 + | Instruction::Reserved47 + | Instruction::Reserved48 + | Instruction::Reserved49 + | Instruction::Reserved50 + | Instruction::Reserved51 + | Instruction::Reserved52 + | Instruction::Reserved53 + | Instruction::Reserved54 => unreachable!("Reserved opcodes are unreachable"), + } + } +} + +impl std::fmt::Display for Operands { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => Ok(()), + Self::Dst { dst } => write!(f, "dst:{dst}"), + Self::LhsRhsDst { lhs, rhs, dst } => write!(f, "lhs:{lhs}, rhs:{rhs}, dst:{dst}"), + Self::RhsIndexDst { rhs, index, dst } => { + write!(f, "rhs:{rhs}, index:{index}, dst:{dst}") + } + Self::SrcDst { src, dst } => write!(f, "src:{src}, dst:{dst}"), + Self::SetFunctionName { + function, + name, + prefix, + } => { + let prefix_str = match prefix { + 1 => "prefix: get", + 2 => "prefix: set", + _ => "prefix:", + }; + write!(f, "function:{function}, name:{name}, {prefix_str}") + } + Self::ValueDst { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::IndexDst { index, dst } => write!(f, "index:{index}, dst:{dst}"), + Self::Message { message } => write!(f, "message:{message}"), + Self::Regexp { + pattern_index, + flags_index, + dst, + } => { + write!(f, "pattern:{pattern_index}, flags:{flags_index}, dst:{dst}") + } + Self::Address { address } => write!(f, "address:{address}"), + Self::AddressValue { address, value } => write!(f, "value:{value}, address:{address}"), + Self::AddressLhsRhs { address, lhs, rhs } => { + write!(f, "lhs:{lhs}, rhs:{rhs}, address:{address}") + } + Self::Case { + address, + value, + condition, + } => { + write!(f, "value:{value}, condition:{condition}, address:{address}") + } + Self::CallEval { + argument_count, + scope_index, + } => { + write!( + f, + "argument_count:{argument_count}, scope_index:{scope_index}" + ) + } + Self::ScopeIndex { scope_index } => write!(f, "scope_index:{scope_index}"), + Self::ArgumentCount { argument_count } => write!(f, "argument_count:{argument_count}"), + Self::BindingIndex { binding_index } => write!(f, "binding_index:{binding_index}"), + Self::SrcBindingIndex { src, binding_index } => { + write!(f, "src:{src}, binding_index:{binding_index}") + } + Self::DstBindingIndex { dst, binding_index } => { + write!(f, "dst:{dst}, binding_index:{binding_index}") + } + Self::GetNameGlobal { + dst, + binding_index, + ic_index, + } => { + write!( + f, + "dst:{dst}, binding_index:{binding_index}, ic_index:{ic_index}" + ) + } + Self::ObjectValueName { + object, + value, + name_index, + } => { + write!(f, "object:{object}, value:{value}, name_index:{name_index}") + } + Self::DstObjectName { + dst, + object, + name_index, + } => { + write!(f, "dst:{dst}, object:{object}, name_index:{name_index}") + } + Self::ObjectProtoValueName { + object, + proto, + value, + name_index, + } => { + write!( + f, + "object:{object}, proto:{proto}, value:{value}, name_index:{name_index}" + ) + } + Self::Index { index } => write!(f, "index:{index}"), + Self::ObjectName { object, name_index } => { + write!(f, "object:{object}, name_index:{name_index}") + } + Self::DstValueIc { + dst, + value, + ic_index, + } => write!(f, "dst:{dst}, value:{value}, ic:{ic_index}"), + Self::DstReceiverValueIc { + dst, + receiver, + value, + ic_index, + } => { + write!( + f, + "dst:{dst}, receiver:{receiver}, value:{value}, ic:{ic_index}" + ) + } + Self::ObjectValueIc { + object, + value, + ic_index, + } => write!(f, "object:{object}, value:{value}, ic:{ic_index}"), + Self::ObjectReceiverValueIc { + object, + receiver, + value, + ic_index, + } => { + write!( + f, + "object:{object}, receiver:{receiver}, value:{value}, ic:{ic_index}" + ) + } + Self::DstKeyReceiverObject { + dst, + key, + receiver, + object, + } => { + write!( + f, + "dst:{dst}, object:{object}, receiver:{receiver}, key:{key}" + ) + } + Self::ObjectReceiverKeyValue { + object, + receiver, + key, + value, + } => { + write!( + f, + "object:{object}, receiver:{receiver}, key:{key}, value:{value}" + ) + } + Self::ObjectKeyValue { object, key, value } => { + write!(f, "object:{object}, key:{key}, value:{value}") + } + Self::ObjectKey { object, key } => write!(f, "object:{object}, key:{key}"), + Self::ValueDone { value, done } => write!(f, "value:{value}, done:{done}"), + Self::DstClassSuperclass { + dst, + class, + superclass, + } => { + write!(f, "dst:{dst}, class:{class}, superclass:{superclass}") + } + Self::DstPrototypeClass { + dst, + prototype, + class, + } => { + write!(f, "dst:{dst}, prototype:{prototype}, class:{class}") + } + Self::FunctionHome { function, home } => write!(f, "function:{function}, home:{home}"), + Self::Function { function } => write!(f, "function:{function}"), + Self::ObjectPrototype { object, prototype } => { + write!(f, "object:{object}, prototype:{prototype}") + } + Self::Object { object } => write!(f, "object:{object}"), + Self::ValueArray { value, array } => write!(f, "value:{value}, array:{array}"), + Self::Array { array } => write!(f, "array:{array}"), + Self::Value { value } => write!(f, "value:{value}"), + Self::SpecifierOptions { specifier, options } => { + write!(f, "specifier:{specifier}, options:{options}") + } + Self::ClassField { + object, + name, + value, + is_anonymous_function, + } => { + write!( + f, + "object:{object}, value:{value}, name:{name}, is_anonymous_function:{is_anonymous_function}" + ) + } + Self::MaybeException { + has_exception, + exception, + } => { + write!(f, "has_exception:{has_exception}, exception:{exception}") + } + Self::Src { src } => write!(f, "src:{src}"), + Self::IteratorNextReg { iterator, next } => { + write!(f, "iterator:{iterator}, next:{next}") + } + Self::Result { result } => write!(f, "result:{result}"), + Self::ResumeKindValue { resume_kind, value } => { + write!(f, "resume_kind:{resume_kind}, value:{value}") + } + Self::ValueCalled { value, called } => write!(f, "value:{value}, called:{called}"), + Self::SrcConfigurableName { + src, + configurable, + name_index, + } => { + write!( + f, + "src:{src}, configurable:{configurable}, name_index:{name_index}" + ) + } + Self::ConfigurableName { + configurable, + name_index, + } => { + write!(f, "configurable:{configurable}, name_index:{name_index}") + } + Self::ClassNames { + class, + name_indices, + } => write!(f, "class:{class}, names:{name_indices:?}"), + Self::AddressSiteDst { address, site, dst } => { + write!(f, "address:{address}, site:{site}, dst:{dst}") + } + Self::JumpTable { index, addresses } => { + use itertools::Itertools; + write!( + f, + "index:{index}, jump_table:({})", + addresses.iter().format(", ") + ) + } + Self::DstValues { dst, values } => write!(f, "dst:{dst}, values:{values:?}"), + Self::ObjectSourceExcluded { + object, + source, + excluded_keys, + } => { + write!( + f, + "object:{object}, source:{source}, excluded_keys:{excluded_keys:?}" + ) + } + Self::SiteDstValues { site, dst, values } => { + write!(f, "site:{site}, dst:{dst}, values:{values:?}") + } + Self::FunctionObject { function_object } => { + write!(f, "function_object:{function_object}") + } + } + } +} diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index a475879fed3..4bb16a70317 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -1,5 +1,174 @@ use std::time::Duration; +use super::{Vm, operands::Operands}; + +use crate::{JsValue, vm::Opcode}; + +struct StackGroup { + value: String, + count: usize, + frame_pointer: Option, +} + +impl StackGroup { + const fn new(value: String, count: usize, fp: Option) -> Self { + Self { + value, + count, + frame_pointer: fp, + } + } +} + +#[derive(Debug, Clone)] +pub struct CallFrameInfo { + pub frame_count: usize, + pub frame_pointer: usize, +} + +#[derive(Debug, Clone)] +pub struct VmDisplayOptions { + max_stack_width: usize, + max_value_len: usize, +} + +/// A snapshot of the current stack at any moment in time +#[derive(Debug, Clone)] +pub struct VmStackTrace { + pub stack: Vec, + pub call_frame_info: CallFrameInfo, + pub display_options: VmDisplayOptions, +} + +impl VmStackTrace { + const DEFAULT_MAX_VALUE_LEN: usize = 18; + const DEFAULT_MAX_STACK_WIDTH: usize = 68; + + pub fn new(vm: &Vm) -> Self { + let display_options = VmDisplayOptions { + max_stack_width: Self::DEFAULT_MAX_STACK_WIDTH, + max_value_len: Self::DEFAULT_MAX_VALUE_LEN, + }; + + let call_frame_info = CallFrameInfo { + frame_count: vm.frames.len(), + frame_pointer: vm.frame().fp as usize, + }; + + Self { + stack: vm.stack.stack.clone(), + display_options, + call_frame_info, + } + } + + fn group(&self) -> (Vec, bool) { + let mut force_truncation = false; + let mut stack_groups: Vec = Vec::default(); + // Lazily group values to avoid eagerly evaluating `raw_value` for the entire stack. + for (idx, v) in self.stack.iter().enumerate().rev() { + let is_frame = self.call_frame_info.frame_pointer == idx; + let raw = raw_value(v); + if !is_frame + && let Some(last_group) = stack_groups.last_mut() + && last_group.value == raw + && last_group.frame_pointer.is_none() + { + last_group.count += 1; + } else { + let marker = if is_frame { + Some(self.call_frame_info.frame_count) + } else { + None + }; + stack_groups.push(StackGroup::new(raw, 1, marker)); + // If groups is large enough to mathematically guarantee overflowing the display width, + // we can stop evaluating to save instruction budget / time. + if stack_groups.len() > Self::DEFAULT_MAX_STACK_WIDTH / 2 { + force_truncation = true; + break; + } + } + } + (stack_groups, force_truncation) + } +} + +impl std::fmt::Display for VmStackTrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.stack.is_empty() { + f.write_str("[ ]")?; + return Ok(()); + } + + let (groups, mut truncated) = self.group(); + + let mut stack_string = String::from("[ "); + + let suffix = format!(".. ({} total) ]", self.call_frame_info.frame_count); + + for ( + i, + StackGroup { + value, + count, + frame_pointer, + }, + ) in groups.iter().enumerate() + { + let displayable_value = truncate_to_len(value, self.display_options.max_value_len); + let part = if *count > 1 { + format!("{displayable_value} (x{count})") + } else { + displayable_value + }; + let separator = if let Some(fc) = frame_pointer { + format!(" |{fc}|") + } else if i + 1 < groups.len() { + ",".to_string() + } else { + String::new() + }; + let addition = format!("{part}{separator} "); + if stack_string.len() + addition.len() + suffix.len() + > self.display_options.max_stack_width + { + truncated = true; + break; + } + stack_string.push_str(&addition); + } + + if truncated { + stack_string.push_str(&suffix); + } else { + stack_string.push(']'); + } + + f.write_str(&stack_string) + } +} + +fn raw_value(value: &JsValue) -> String { + match value { + v if v.is_callable() => "func".to_string(), + v if v.is_object() => "obj".to_string(), + v if v.is_undefined() => "und".to_string(), + v if v.is_null() => "null".to_string(), + v => v.display().to_string(), + } +} + +fn truncate_to_len(val: &str, max_len: usize) -> String { + if val.len() <= max_len { + return val.to_string(); + } + let mut end = max_len - 2; + while !val.is_char_boundary(end) && end > 0 { + end -= 1; + } + format!("{}..", &val[..end]) +} /// The call frame name /// /// This will have the name of the call frame provided or `Global` it's @@ -25,10 +194,10 @@ pub struct CallFrameMessage { /// A message that emits instruction execution details about a call frame #[derive(Debug, Clone)] pub struct OpcodeExecutionMessage { - pub opcode: &'static str, + pub opcode: Opcode, pub duration: Duration, - pub operands: String, - pub stack: String, + pub operands: Operands, + pub stack_trace: VmStackTrace, } /// The various events that are emitted from Boa's virtual machine. @@ -110,11 +279,13 @@ impl VirtualMachineTracer for StdoutTracer { opcode, duration, operands, - stack, + stack_trace, } = execution_message; + let opcode = opcode.as_str(); + println!( - "{: Date: Mon, 7 Sep 2026 15:06:33 -0500 Subject: [PATCH 3/6] General updates to tracing logic + updates for insta tests --- .../src/bytecompiler/expression/binary.rs | 2 +- core/engine/src/bytecompiler/jump_control.rs | 2 +- core/engine/src/bytecompiler/mod.rs | 3 +- core/engine/src/bytecompiler/register.rs | 2 +- core/engine/src/vm/code_block.rs | 6 +- core/engine/src/vm/mod.rs | 63 +- core/engine/src/vm/opcode/mod.rs | 158 +-- core/engine/src/vm/operands.rs | 942 ++++++++++-------- core/engine/src/vm/trace.rs | 35 +- tests/insta-bytecode/Cargo.toml | 2 +- tests/insta-bytecode/src/lib.rs | 43 +- ...ecode__compile_bytecode@basic-loop.js.snap | 4 +- ...pile_bytecode@double-loop-function.js.snap | 24 +- ...pile_bytecode@generator-yield-star.js.snap | 201 +++- ...compile_bytecode@if-ternary-branch.js.snap | 4 +- ...de__compile_bytecode@loop-hoisting.js.snap | 48 +- ...sta_bytecode__compile_bytecode@new.js.snap | 43 +- ...code__compile_bytecode@try-finally.js.snap | 4 +- 18 files changed, 984 insertions(+), 602 deletions(-) diff --git a/core/engine/src/bytecompiler/expression/binary.rs b/core/engine/src/bytecompiler/expression/binary.rs index b603c149cf8..8d2b996341e 100644 --- a/core/engine/src/bytecompiler/expression/binary.rs +++ b/core/engine/src/bytecompiler/expression/binary.rs @@ -1,6 +1,6 @@ use crate::{ bytecompiler::{ByteCompiler, Label, Register}, - vm::opcode::RegisterOperand, + vm::operands::RegisterOperand, }; use boa_ast::{ Expression, diff --git a/core/engine/src/bytecompiler/jump_control.rs b/core/engine/src/bytecompiler/jump_control.rs index 4fc1020a306..6caa5c4a775 100644 --- a/core/engine/src/bytecompiler/jump_control.rs +++ b/core/engine/src/bytecompiler/jump_control.rs @@ -12,7 +12,7 @@ use super::Register; use crate::{ bytecompiler::{ByteCompiler, Label}, - vm::{CallFrame, Handler, opcode::Address}, + vm::{CallFrame, Handler, operands::Address}, }; use bitflags::bitflags; use boa_interner::Sym; diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 3159e2f062d..cb3181875bb 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -47,7 +47,8 @@ use crate::{ vm::{ CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, GlobalFunctionBinding, Handler, InlineCache, - opcode::{Address, BindingOpcode, BytecodeEmitter, RegisterOperand}, + opcode::{BindingOpcode, BytecodeEmitter}, + operands::{Address, RegisterOperand}, source_info::{SourceInfo, SourceMap, SourceMapBuilder, SourcePath}, }, }; diff --git a/core/engine/src/bytecompiler/register.rs b/core/engine/src/bytecompiler/register.rs index ecc8f7f63a8..b4aa83a1920 100644 --- a/core/engine/src/bytecompiler/register.rs +++ b/core/engine/src/bytecompiler/register.rs @@ -1,4 +1,4 @@ -use crate::vm::opcode::RegisterOperand; +use crate::vm::operands::RegisterOperand; use std::mem::forget; bitflags::bitflags! { diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 6c6f0c4a2b8..d6b67536d39 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -18,8 +18,8 @@ use thin_vec::ThinVec; use super::{ InlineCache, - opcode::{Address, ByteCode, InstructionIterator}, - operands::Operands, + opcode::{Bytecode, InstructionIterator}, + operands::{Address, OperandsShape}, source_info::{SourceInfo, SourceMap, SourcePath}, }; @@ -391,7 +391,7 @@ impl Display for CodeBlock { let mut iterator = InstructionIterator::new(&self.bytecode); while let Some((instruction_start_pc, opcode, instruction)) = iterator.next() { let opcode = opcode.as_str(); - let operands = Operands::from_instruction(&instruction); + let operands = OperandsShape::from_instruction(&instruction); let pc = iterator.pc(); let handler = if let Some((i, handler)) = self.find_handler(instruction_start_pc as u32) { diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 356a8237cc4..70f1c0488ff 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -20,11 +20,13 @@ use shadow_stack::ShadowStack; use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task}; #[cfg(feature = "trace")] -pub use trace::{EmptyTracer, StdoutTracer, VirtualMachineTracer}; +pub use trace::{EmptyTracer, StdoutTracer, VirtualMachineEvent, VirtualMachineTracer}; #[cfg(feature = "trace")] use crate::sys::time::Instant; +pub use operands::{Address, IndexOperand, RegisterOperand}; + #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode}; @@ -56,10 +58,12 @@ pub(crate) mod opcode; pub(crate) mod shadow_stack; pub(crate) mod source_info; -mod operands; +/// Operand types specific to Boa's virtual machine +pub mod operands; +/// Boa's virtual machine tracing types and logic #[cfg(feature = "trace")] -mod trace; +pub mod trace; #[cfg(feature = "flowgraph")] pub mod flowgraph; @@ -596,18 +600,29 @@ impl Context { self.vm.tracer = tracer; } + pub(crate) fn walk_code_block(&self, code_block: &Gc) { + use crate::vm::trace::CallFrameMessage; + if !code_block.traced.get() { + let call_frame_message = CallFrameMessage { + bytecode: code_block.to_string(), + }; + self.vm + .tracer + .emit_event(VirtualMachineEvent::CallFrameTrace(call_frame_message)); + code_block.traced.set(true); + + for constant in &code_block.constants { + if let Constant::Function(code_block) = constant { + self.walk_code_block(code_block); + } + } + } + } + pub(crate) fn trace_call_frame(&self) { - use crate::vm::trace::{ - CallFrameMessage, CallFrameName, ExecutionStartMessage, VirtualMachineEvent, - }; + use crate::vm::trace::{CallFrameName, ExecutionStartMessage, VirtualMachineEvent}; let frame = self.vm.frame(); - let call_frame_message = CallFrameMessage { - bytecode: frame.code_block.to_string(), - }; - self.vm - .tracer - .emit_event(VirtualMachineEvent::CallFrameTrace(call_frame_message)); - + self.walk_code_block(frame.code_block()); let call_frame_name = if self.vm.frames.is_empty() { CallFrameName::Global } else { @@ -628,7 +643,7 @@ impl Context { where F: FnOnce(&mut Context, Opcode) -> ControlFlow, { - use crate::vm::operands::Operands; + use crate::vm::operands::OperandsShape; use crate::vm::trace::{OpcodeExecutionMessage, VirtualMachineEvent, VmStackTrace}; let frame = self.vm.frame(); @@ -636,7 +651,11 @@ impl Context { .code_block .bytecode .next_instruction(frame.pc as usize); - let operands = Operands::from_instruction(&instruction); + let operands = OperandsShape::from_instruction(&instruction); + + let instant = Instant::now(); + let result = self.execute_instruction(f, opcode); + let duration = instant.elapsed(); match opcode { Opcode::Call @@ -656,17 +675,13 @@ impl Context { _ => {} } - let instant = Instant::now(); - let result = self.execute_instruction(f, opcode); - let duration = instant.elapsed(); - let stack_trace = VmStackTrace::new(&self.vm); self.vm .tracer .emit_event(VirtualMachineEvent::ExecutionTrace( OpcodeExecutionMessage { - opcode, + opcode: opcode.as_str(), duration, operands, stack_trace, @@ -863,6 +878,10 @@ impl Context { pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord { let mut runtime_budget: u32 = budget; + if self.vm.trace { + self.trace_call_frame(); + } + while let Some(byte) = self .vm .frame() @@ -896,6 +915,10 @@ impl Context { } pub(crate) fn run(&mut self) -> CompletionRecord { + if self.vm.trace { + self.trace_call_frame(); + } + while let Some(byte) = self .vm .frame() diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 0a70248d330..568a866a847 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -15,7 +15,11 @@ //! [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-evaluation use crate::{ Context, - vm::{completion_record::CompletionRecord, completion_record::IntoCompletionRecord}, + vm::{ + completion_record::CompletionRecord, + completion_record::IntoCompletionRecord, + operands::{Address, IndexOperand, RegisterOperand}, + }, }; use args::{Argument, read}; use std::ops::ControlFlow; @@ -187,158 +191,6 @@ pub(crate) struct Bytecode { pub(crate) bytes: Box<[u8]>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -/// An address is a bytecode offset, displayed as hexadecimal. -pub(crate) struct Address(u32); - -impl Address { - /// Create a new [`Address`] from a u32 value. - pub(crate) const fn new(value: u32) -> Self { - Self(value) - } - - /// Returns the inner `u32` value. - pub(crate) const fn as_u32(self) -> u32 { - self.0 - } -} - -impl From
for u32 { - fn from(addr: Address) -> Self { - addr.0 - } -} - -impl From for Address { - fn from(value: u32) -> Self { - Self::new(value) - } -} - -impl std::ops::Add for Address { - type Output = Self; - - fn add(self, rhs: u32) -> Self { - Self::new(self.0 + rhs) - } -} - -impl std::fmt::Display for Address { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:06x}", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -/// A register operand is a register index used in bytecode instructions. -pub(crate) struct RegisterOperand(u32); - -impl std::ops::Deref for RegisterOperand { - type Target = u32; - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl RegisterOperand { - /// Create a new [`RegisterOperand`] from a u32 value. - pub(crate) fn new(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: RegisterOperand) -> Self { - value.0 - } -} - -impl From for usize { - fn from(value: RegisterOperand) -> Self { - value.0 as usize - } -} - -impl From for RegisterOperand { - fn from(value: u8) -> Self { - Self::new(value.into()) - } -} - -impl From for RegisterOperand { - fn from(value: u16) -> Self { - Self::new(value.into()) - } -} - -impl From for RegisterOperand { - fn from(value: u32) -> Self { - Self::new(value) - } -} - -impl std::fmt::Display for RegisterOperand { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "r{:02}", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -/// A index operand is e.g. an index into the constant pool -pub(crate) struct IndexOperand(u32); - -impl IndexOperand { - /// Create a new [`IndexOperand`] from a u32 value. - pub(crate) fn new(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: IndexOperand) -> Self { - value.0 - } -} - -impl From for usize { - fn from(value: IndexOperand) -> Self { - value.0 as usize - } -} - -impl From for IndexOperand { - fn from(value: bool) -> Self { - Self::new(value.into()) - } -} - -impl From for IndexOperand { - fn from(value: u8) -> Self { - Self::new(value.into()) - } -} - -impl From for IndexOperand { - fn from(value: u16) -> Self { - Self::new(value.into()) - } -} - -impl From for IndexOperand { - fn from(value: u32) -> Self { - Self::new(value) - } -} - -impl std::fmt::Display for IndexOperand { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - impl Opcode { fn encode(self) -> u8 { self as u8 diff --git a/core/engine/src/vm/operands.rs b/core/engine/src/vm/operands.rs index b24cc7825c5..d1161436482 100644 --- a/core/engine/src/vm/operands.rs +++ b/core/engine/src/vm/operands.rs @@ -1,269 +1,432 @@ use crate::vm::Instruction; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// An address is a bytecode offset, displayed as hexadecimal. +pub struct Address(pub(crate) u32); + +impl Address { + /// Create a new [`Address`] from a u32 value. + pub(crate) const fn new(value: u32) -> Self { + Self(value) + } + + /// Returns the inner `u32` value. + pub(crate) const fn as_u32(self) -> u32 { + self.0 + } +} + +impl From
for u32 { + fn from(addr: Address) -> Self { + addr.0 + } +} + +impl From for Address { + fn from(value: u32) -> Self { + Self::new(value) + } +} + +impl std::ops::Add for Address { + type Output = Self; + + fn add(self, rhs: u32) -> Self { + Self::new(self.0 + rhs) + } +} + +impl std::fmt::Display for Address { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:06x}", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A register operand is a register index used in bytecode instructions. +pub struct RegisterOperand(pub(crate) u32); + +impl RegisterOperand { + /// Create a new [`RegisterOperand`] from a u32 value. + pub(crate) fn new(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: RegisterOperand) -> Self { + value.0 + } +} + +impl From for usize { + fn from(value: RegisterOperand) -> Self { + value.0 as usize + } +} + +impl From for RegisterOperand { + fn from(value: u8) -> Self { + Self::new(value.into()) + } +} + +impl From for RegisterOperand { + fn from(value: u16) -> Self { + Self::new(value.into()) + } +} + +impl From for RegisterOperand { + fn from(value: u32) -> Self { + Self::new(value) + } +} + +impl std::fmt::Display for RegisterOperand { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "r{:02}", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A index operand is e.g. an index into the constant pool +pub struct IndexOperand(pub(crate) u32); + +impl IndexOperand { + /// Create a new [`IndexOperand`] from a u32 value. + pub(crate) fn new(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: IndexOperand) -> Self { + value.0 + } +} + +impl From for usize { + fn from(value: IndexOperand) -> Self { + value.0 as usize + } +} + +impl From for IndexOperand { + fn from(value: bool) -> Self { + Self::new(value.into()) + } +} + +impl From for IndexOperand { + fn from(value: u8) -> Self { + Self::new(value.into()) + } +} + +impl From for IndexOperand { + fn from(value: u16) -> Self { + Self::new(value.into()) + } +} + +impl From for IndexOperand { + fn from(value: u32) -> Self { + Self::new(value) + } +} + +impl std::fmt::Display for IndexOperand { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + /// Available Operands types that Boa's VM uses +#[expect(missing_docs)] #[derive(Clone, Debug, PartialEq)] -pub enum Operands { +pub enum OperandsShape { None, Dst { - dst: u32, + dst: RegisterOperand, }, LhsRhsDst { - lhs: u32, - rhs: u32, - dst: u32, + lhs: RegisterOperand, + rhs: RegisterOperand, + dst: RegisterOperand, }, RhsIndexDst { - rhs: u32, - index: u32, - dst: u32, + rhs: RegisterOperand, + index: IndexOperand, + dst: RegisterOperand, }, SrcDst { - src: u32, - dst: u32, + src: RegisterOperand, + dst: RegisterOperand, }, SetFunctionName { - function: u32, - name: u32, + function: RegisterOperand, + name: RegisterOperand, prefix: u8, }, - ValueDst { + ValueDstI8 { + value: i8, + dst: RegisterOperand, + }, + ValueDstI16 { + value: i16, + dst: RegisterOperand, + }, + ValueDstI32 { + value: i32, + dst: RegisterOperand, + }, + ValueDstF32 { + value: f32, + dst: RegisterOperand, + }, + ValueDstF64 { value: f64, - dst: u32, + dst: RegisterOperand, }, IndexDst { - index: u32, - dst: u32, + index: IndexOperand, + dst: RegisterOperand, }, Message { - message: u32, + message: IndexOperand, }, Regexp { - pattern_index: u32, - flags_index: u32, - dst: u32, + pattern_index: IndexOperand, + flags_index: IndexOperand, + dst: RegisterOperand, }, Address { - address: u32, + address: Address, }, AddressValue { - address: u32, - value: u32, + address: Address, + value: RegisterOperand, }, AddressLhsRhs { - address: u32, - lhs: u32, - rhs: u32, + address: Address, + lhs: RegisterOperand, + rhs: RegisterOperand, }, Case { - address: u32, - value: u32, - condition: u32, + address: Address, + value: RegisterOperand, + condition: RegisterOperand, }, CallEval { - argument_count: u32, - scope_index: u32, + argument_count: IndexOperand, + scope_index: IndexOperand, }, ScopeIndex { - scope_index: u32, + scope_index: IndexOperand, }, ArgumentCount { - argument_count: u32, + argument_count: IndexOperand, }, BindingIndex { - binding_index: u32, + binding_index: IndexOperand, }, SrcBindingIndex { - src: u32, - binding_index: u32, + src: RegisterOperand, + binding_index: IndexOperand, }, DstBindingIndex { - dst: u32, - binding_index: u32, + dst: RegisterOperand, + binding_index: IndexOperand, }, GetNameGlobal { - dst: u32, - binding_index: u32, - ic_index: u32, + dst: RegisterOperand, + binding_index: IndexOperand, + ic_index: IndexOperand, }, ObjectValueName { - object: u32, - value: u32, - name_index: u32, + object: RegisterOperand, + value: RegisterOperand, + name_index: IndexOperand, }, DstObjectName { - dst: u32, - object: u32, - name_index: u32, + dst: RegisterOperand, + object: RegisterOperand, + name_index: IndexOperand, }, ObjectProtoValueName { - object: u32, - proto: u32, - value: u32, - name_index: u32, + object: RegisterOperand, + proto: RegisterOperand, + value: RegisterOperand, + name_index: IndexOperand, }, Index { - index: u32, + index: IndexOperand, }, ObjectName { - object: u32, - name_index: u32, + object: RegisterOperand, + name_index: IndexOperand, }, DstValueIc { - dst: u32, - value: u32, - ic_index: u32, + dst: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, }, DstReceiverValueIc { - dst: u32, - receiver: u32, - value: u32, - ic_index: u32, + dst: RegisterOperand, + receiver: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, }, ObjectValueIc { - object: u32, - value: u32, - ic_index: u32, + object: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, }, ObjectReceiverValueIc { - object: u32, - receiver: u32, - value: u32, - ic_index: u32, + object: RegisterOperand, + receiver: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, }, DstKeyReceiverObject { - dst: u32, - key: u32, - receiver: u32, - object: u32, + dst: RegisterOperand, + key: RegisterOperand, + receiver: RegisterOperand, + object: RegisterOperand, }, ObjectReceiverKeyValue { - object: u32, - receiver: u32, - key: u32, - value: u32, + object: RegisterOperand, + receiver: RegisterOperand, + key: RegisterOperand, + value: RegisterOperand, }, ObjectKeyValue { - object: u32, - key: u32, - value: u32, + object: RegisterOperand, + key: RegisterOperand, + value: RegisterOperand, }, ObjectKey { - object: u32, - key: u32, + object: RegisterOperand, + key: RegisterOperand, }, ValueDone { - value: u32, - done: u32, + value: RegisterOperand, + done: IndexOperand, }, DstClassSuperclass { - dst: u32, - class: u32, - superclass: u32, + dst: RegisterOperand, + class: RegisterOperand, + superclass: RegisterOperand, }, DstPrototypeClass { - dst: u32, - prototype: u32, - class: u32, + dst: RegisterOperand, + prototype: RegisterOperand, + class: RegisterOperand, }, FunctionHome { - function: u32, - home: u32, + function: RegisterOperand, + home: RegisterOperand, }, Function { - function: u32, + function: RegisterOperand, }, ObjectPrototype { - object: u32, - prototype: u32, + object: RegisterOperand, + prototype: RegisterOperand, }, Object { - object: u32, + object: RegisterOperand, }, ValueArray { - value: u32, - array: u32, + value: RegisterOperand, + array: RegisterOperand, }, Array { - array: u32, + array: RegisterOperand, }, Value { - value: u32, + value: RegisterOperand, }, SpecifierOptions { - specifier: u32, - options: u32, + specifier: RegisterOperand, + options: RegisterOperand, + phase: IndexOperand, }, ClassField { - object: u32, - name: u32, - value: u32, - is_anonymous_function: u32, + object: RegisterOperand, + name: RegisterOperand, + value: RegisterOperand, + is_anonymous_function: IndexOperand, }, MaybeException { - has_exception: u32, - exception: u32, + has_exception: RegisterOperand, + exception: RegisterOperand, }, Src { - src: u32, + src: RegisterOperand, }, IteratorNextReg { - iterator: u32, - next: u32, + iterator: RegisterOperand, + next: RegisterOperand, }, Result { - result: u32, + result: RegisterOperand, }, ResumeKindValue { - resume_kind: u32, - value: u32, + resume_kind: RegisterOperand, + value: RegisterOperand, }, ValueCalled { - value: u32, - called: u32, + value: RegisterOperand, + called: RegisterOperand, }, SrcConfigurableName { - src: u32, - configurable: u32, - name_index: u32, + src: RegisterOperand, + configurable: RegisterOperand, + name_index: IndexOperand, }, ConfigurableName { configurable: bool, - name_index: u32, + name_index: IndexOperand, }, ClassNames { - class: u32, + class: RegisterOperand, name_indices: Box<[u32]>, }, AddressSiteDst { - address: u32, + address: Address, site: u64, - dst: u32, + dst: RegisterOperand, }, JumpTable { index: u32, - addresses: Box<[u32]>, + addresses: Box<[Address]>, }, DstValues { - dst: u32, - values: Box<[u32]>, + dst: RegisterOperand, + values: Box<[RegisterOperand]>, }, ObjectSourceExcluded { - object: u32, - source: u32, - excluded_keys: Box<[u32]>, + object: RegisterOperand, + source: RegisterOperand, + excluded_keys: Box<[RegisterOperand]>, }, SiteDstValues { site: u64, - dst: u32, + dst: RegisterOperand, values: Box<[u32]>, }, FunctionObject { - function_object: u32, + function_object: RegisterOperand, }, } -impl Operands { - pub fn from_instruction(instruction: &Instruction) -> Self { +impl OperandsShape { + pub(crate) fn from_instruction(instruction: &Instruction) -> Self { match instruction { Instruction::Pop | Instruction::DeleteSuperThrow @@ -281,27 +444,7 @@ impl Operands { | Instruction::SuperCallSpread | Instruction::PopPrivateEnvironment | Instruction::Generator - | Instruction::AsyncGenerator => Operands::None, - - Instruction::SetRegisterFromAccumulator { dst } - | Instruction::PopIntoRegister { dst } - | Instruction::PushZero { dst } - | Instruction::PushOne { dst } - | Instruction::PushNan { dst } - | Instruction::PushPositiveInfinity { dst } - | Instruction::PushNegativeInfinity { dst } - | Instruction::PushNull { dst } - | Instruction::PushTrue { dst } - | Instruction::PushFalse { dst } - | Instruction::PushUndefined { dst } - | Instruction::Exception { dst } - | Instruction::This { dst } - | Instruction::NewTarget { dst } - | Instruction::ImportMeta { dst } - | Instruction::CreateMappedArgumentsObject { dst } - | Instruction::CreateUnmappedArgumentsObject { dst } - | Instruction::RestParameterInit { dst } - | Instruction::PushNewArray { dst } => Operands::Dst { dst: **dst }, + | Instruction::AsyncGenerator => OperandsShape::None, Instruction::Add { lhs, rhs, dst } | Instruction::Sub { lhs, rhs, dst } @@ -324,86 +467,80 @@ impl Operands { | Instruction::GreaterThanOrEq { lhs, rhs, dst } | Instruction::LessThan { lhs, rhs, dst } | Instruction::LessThanOrEq { lhs, rhs, dst } - | Instruction::InstanceOf { lhs, rhs, dst } => Operands::LhsRhsDst { - lhs: **lhs, - rhs: **rhs, - dst: **dst, + | Instruction::InstanceOf { lhs, rhs, dst } => OperandsShape::LhsRhsDst { + lhs: *lhs, + rhs: *rhs, + dst: *dst, }, - Instruction::InPrivate { dst, index, rhs } => Operands::RhsIndexDst { - rhs: **rhs, - index: **index, - dst: **dst, + Instruction::InPrivate { dst, index, rhs } => OperandsShape::RhsIndexDst { + rhs: *rhs, + index: *index, + dst: *dst, }, Instruction::Inc { src, dst } | Instruction::Dec { src, dst } | Instruction::Move { src, dst } - | Instruction::ToPropertyKey { src, dst } => Operands::SrcDst { - src: u32::from(*src), - dst: **dst, + | Instruction::ToInt32 { src, dst } + | Instruction::ToPropertyKey { src, dst } => OperandsShape::SrcDst { + src: *src, + dst: *dst, }, Instruction::SetFunctionName { function, name, prefix, - } => Operands::SetFunctionName { - function: **function, - name: **name, + } => OperandsShape::SetFunctionName { + function: *function, + name: *name, prefix: u32::from(*prefix) as u8, }, - - Instruction::PushInt8 { value, dst } => Operands::ValueDst { - value: f64::from(*value), - dst: **dst, - }, - Instruction::PushInt16 { value, dst } => Operands::ValueDst { - value: f64::from(*value), - dst: **dst, - }, - Instruction::PushInt32 { value, dst } => Operands::ValueDst { - value: f64::from(*value), - dst: **dst, - }, - Instruction::PushFloat { value, dst } => Operands::ValueDst { - value: f64::from(*value), - dst: **dst, - }, - Instruction::PushDouble { value, dst } => Operands::ValueDst { - value: *value, - dst: **dst, - }, - - Instruction::PushLiteral { index, dst } - | Instruction::ThisForObjectEnvironmentName { index, dst } + Instruction::ThisForObjectEnvironmentName { index, dst } | Instruction::GetFunction { index, dst } - | Instruction::HasRestrictedGlobalProperty { index, dst } - | Instruction::CanDeclareGlobalFunction { index, dst } - | Instruction::CanDeclareGlobalVar { index, dst } - | Instruction::GetArgument { index, dst } => Operands::IndexDst { - index: **index, - dst: **dst, + | Instruction::StoreLiteral { index, dst } + | Instruction::GetArgument { index, dst } => OperandsShape::IndexDst { + index: *index, + dst: *dst, }, Instruction::ThrowNewTypeError { message } - | Instruction::ThrowNewSyntaxError { message } | Instruction::ThrowNewReferenceError { message } => { - Operands::Message { message: **message } + OperandsShape::Message { message: *message } } - Instruction::PushRegexp { - pattern_index, - flags_index, - dst, - } => Operands::Regexp { - pattern_index: **pattern_index, - flags_index: **flags_index, - dst: **dst, + Instruction::Jump { address } => OperandsShape::Address { address: *address }, + + Instruction::StoreInt8 { value, dst } => OperandsShape::ValueDstI8 { + value: *value, + dst: *dst, + }, + Instruction::StoreInt16 { value, dst } => OperandsShape::ValueDstI16 { + value: *value, + dst: *dst, + }, + Instruction::StoreInt32 { value, dst } => OperandsShape::ValueDstI32 { + value: *value, + dst: *dst, + }, + Instruction::StoreFloat { value, dst } => OperandsShape::ValueDstF32 { + value: *value, + dst: *dst, + }, + Instruction::StoreDouble { value, dst } => OperandsShape::ValueDstF64 { + value: *value, + dst: *dst, }, - Instruction::Jump { address } => Operands::Address { - address: u32::from(*address), + Instruction::StoreClassPrototype { + dst, + class, + superclass, + } => OperandsShape::DstClassSuperclass { + dst: *dst, + class: *class, + superclass: *superclass, }, Instruction::JumpIfTrue { address, value } @@ -412,79 +549,79 @@ impl Operands { | Instruction::JumpIfNullOrUndefined { address, value } | Instruction::LogicalAnd { address, value } | Instruction::LogicalOr { address, value } - | Instruction::Coalesce { address, value } => Operands::AddressValue { - address: u32::from(*address), - value: **value, + | Instruction::Coalesce { address, value } => OperandsShape::AddressValue { + address: *address, + value: *value, }, Instruction::JumpIfNotLessThan { address, lhs, rhs } | Instruction::JumpIfNotLessThanOrEqual { address, lhs, rhs } | Instruction::JumpIfNotGreaterThan { address, lhs, rhs } | Instruction::JumpIfNotGreaterThanOrEqual { address, lhs, rhs } - | Instruction::JumpIfNotEqual { address, lhs, rhs } => Operands::AddressLhsRhs { - address: u32::from(*address), - lhs: **lhs, - rhs: **rhs, + | Instruction::JumpIfNotEqual { address, lhs, rhs } => OperandsShape::AddressLhsRhs { + address: *address, + lhs: *lhs, + rhs: *rhs, }, Instruction::Case { address, value, condition, - } => Operands::Case { - address: u32::from(*address), - value: **value, - condition: **condition, + } => OperandsShape::Case { + address: *address, + value: *value, + condition: *condition, }, Instruction::CallEval { argument_count, scope_index, - } => Operands::CallEval { - argument_count: **argument_count, - scope_index: **scope_index, + } => OperandsShape::CallEval { + argument_count: *argument_count, + scope_index: *scope_index, }, Instruction::CallEvalSpread { scope_index } - | Instruction::PushScope { scope_index } => Operands::ScopeIndex { - scope_index: **scope_index, + | Instruction::PushScope { scope_index } => OperandsShape::ScopeIndex { + scope_index: *scope_index, }, Instruction::Call { argument_count } | Instruction::New { argument_count } - | Instruction::SuperCall { argument_count } => Operands::ArgumentCount { - argument_count: **argument_count, + | Instruction::SuperCall { argument_count } => OperandsShape::ArgumentCount { + argument_count: *argument_count, }, - Instruction::DefVar { binding_index } | Instruction::GetLocator { binding_index } => { - Operands::BindingIndex { - binding_index: **binding_index, - } - } + Instruction::DefVar { binding_index } + | Instruction::DefEvalVar { binding_index } + | Instruction::GetLocator { binding_index } => OperandsShape::BindingIndex { + binding_index: *binding_index, + }, Instruction::DefInitVar { src, binding_index } | Instruction::PutLexicalValue { src, binding_index } - | Instruction::SetName { src, binding_index } => Operands::SrcBindingIndex { - src: u32::from(*src), - binding_index: **binding_index, + | Instruction::SetName { src, binding_index } => OperandsShape::SrcBindingIndex { + src: *src, + binding_index: *binding_index, }, Instruction::GetName { dst, binding_index } | Instruction::GetNameAndLocator { dst, binding_index } | Instruction::GetNameOrUndefined { dst, binding_index } - | Instruction::DeleteName { dst, binding_index } => Operands::DstBindingIndex { - dst: **dst, - binding_index: **binding_index, + | Instruction::DeleteName { dst, binding_index } => OperandsShape::DstBindingIndex { + dst: *dst, + binding_index: *binding_index, }, Instruction::GetNameGlobal { dst, binding_index, ic_index, - } => Operands::GetNameGlobal { - dst: **dst, - binding_index: **binding_index, - ic_index: **ic_index, + } => OperandsShape::GetNameGlobal { + dst: *dst, + binding_index: *binding_index, + ic_index: *ic_index, }, Instruction::DefineOwnPropertyByName { @@ -571,36 +708,36 @@ impl Operands { object, value, name_index, - } => Operands::ObjectValueName { - object: **object, - value: **value, - name_index: **name_index, + } => OperandsShape::ObjectValueName { + object: *object, + value: *value, + name_index: *name_index, }, Instruction::GetPrivateField { dst, object, name_index, - } => Operands::DstObjectName { - dst: **dst, - object: **object, - name_index: **name_index, + } => OperandsShape::DstObjectName { + dst: *dst, + object: *object, + name_index: *name_index, }, Instruction::PushClassPrivateMethod { object, proto, value, name_index, - } => Operands::ObjectProtoValueName { - object: **object, - proto: **proto, - value: **value, - name_index: **name_index, + } => OperandsShape::ObjectProtoValueName { + object: *object, + proto: *proto, + value: *value, + name_index: *name_index, }, - Instruction::ThrowMutateImmutable { index } => Operands::Index { index: **index }, + Instruction::ThrowMutateImmutable { index } => OperandsShape::Index { index: *index }, Instruction::DeletePropertyByName { object, name_index } - | Instruction::GetMethod { object, name_index } => Operands::ObjectName { - object: **object, - name_index: **name_index, + | Instruction::GetMethod { object, name_index } => OperandsShape::ObjectName { + object: *object, + name_index: *name_index, }, Instruction::GetLengthProperty { dst, @@ -611,41 +748,41 @@ impl Operands { dst, value, ic_index, - } => Operands::DstValueIc { - dst: **dst, - value: **value, - ic_index: **ic_index, + } => OperandsShape::DstValueIc { + dst: *dst, + value: *value, + ic_index: *ic_index, }, Instruction::GetPropertyByNameWithThis { dst, receiver, value, ic_index, - } => Operands::DstReceiverValueIc { - dst: **dst, - receiver: **receiver, - value: **value, - ic_index: **ic_index, + } => OperandsShape::DstReceiverValueIc { + dst: *dst, + receiver: *receiver, + value: *value, + ic_index: *ic_index, }, Instruction::SetPropertyByName { value, object, ic_index, - } => Operands::ObjectValueIc { - object: **object, - value: **value, - ic_index: **ic_index, + } => OperandsShape::ObjectValueIc { + object: *object, + value: *value, + ic_index: *ic_index, }, Instruction::SetPropertyByNameWithThis { value, receiver, object, ic_index, - } => Operands::ObjectReceiverValueIc { - object: **object, - receiver: **receiver, - value: **value, - ic_index: **ic_index, + } => OperandsShape::ObjectReceiverValueIc { + object: *object, + receiver: *receiver, + value: *value, + ic_index: *ic_index, }, Instruction::GetPropertyByValue { dst, @@ -658,22 +795,22 @@ impl Operands { key, receiver, object, - } => Operands::DstKeyReceiverObject { - dst: **dst, - key: **key, - receiver: **receiver, - object: **object, + } => OperandsShape::DstKeyReceiverObject { + dst: *dst, + key: *key, + receiver: *receiver, + object: *object, }, Instruction::SetPropertyByValue { value, key, receiver, object, - } => Operands::ObjectReceiverKeyValue { - object: **object, - receiver: **receiver, - key: **key, - value: **value, + } => OperandsShape::ObjectReceiverKeyValue { + object: *object, + receiver: *receiver, + key: *key, + value: *value, }, Instruction::DefineOwnPropertyByValue { value, key, object } | Instruction::DefineClassStaticMethodByValue { value, key, object } @@ -684,84 +821,80 @@ impl Operands { | Instruction::SetPropertySetterByValue { value, key, object } | Instruction::DefineClassStaticSetterByValue { value, key, object } | Instruction::DefineClassSetterByValue { value, key, object } => { - Operands::ObjectKeyValue { - object: **object, - key: **key, - value: **value, + OperandsShape::ObjectKeyValue { + object: *object, + key: *key, + value: *value, } } - Instruction::DeletePropertyByValue { key, object } => Operands::ObjectKey { - object: **object, - key: **key, - }, - Instruction::CreateIteratorResult { value, done } => Operands::ValueDone { - value: **value, - done: **done, + Instruction::DeletePropertyByValue { key, object } => OperandsShape::ObjectKey { + object: *object, + key: *key, }, - Instruction::PushClassPrototype { - dst, - class, - superclass, - } => Operands::DstClassSuperclass { - dst: **dst, - class: **class, - superclass: **superclass, + Instruction::CreateIteratorResult { value, done } => OperandsShape::ValueDone { + value: *value, + done: *done, }, Instruction::SetClassPrototype { dst, prototype, class, - } => Operands::DstPrototypeClass { - dst: u32::from(**dst), - prototype: **prototype, - class: **class, + } => OperandsShape::DstPrototypeClass { + dst: *dst, + prototype: *prototype, + class: *class, }, - Instruction::SetHomeObject { function, home } => Operands::FunctionHome { - function: **function, - home: **home, + Instruction::SetHomeObject { function, home } => OperandsShape::FunctionHome { + function: *function, + home: *home, }, - Instruction::GetHomeObject { function } => Operands::Function { - function: **function, + Instruction::GetHomeObject { function } => OperandsShape::Function { + function: *function, }, - Instruction::SetPrototype { object, prototype } => Operands::ObjectPrototype { - object: **object, - prototype: **prototype, + Instruction::SetPrototype { object, prototype } => OperandsShape::ObjectPrototype { + object: *object, + prototype: *prototype, }, - Instruction::GetPrototype { object } => Operands::Object { object: **object }, - Instruction::PushValueToArray { value, array } => Operands::ValueArray { - value: **value, - array: **array, + Instruction::GetPrototype { object } => OperandsShape::Object { object: *object }, + Instruction::PushValueToArray { value, array } => OperandsShape::ValueArray { + value: *value, + array: *array, }, Instruction::PushElisionToArray { array } - | Instruction::PushIteratorToArray { array } => Operands::Array { array: **array }, + | Instruction::PushIteratorToArray { array } => OperandsShape::Array { array: *array }, Instruction::TypeOf { value } | Instruction::LogicalNot { value } | Instruction::Pos { value } | Instruction::Neg { value } | Instruction::IsObject { value } | Instruction::BindThisValue { value } - | Instruction::BitNot { value } => Operands::Value { value: **value }, - Instruction::ImportCall { specifier, options } => Operands::SpecifierOptions { - specifier: **specifier, - options: **options, + | Instruction::BitNot { value } => OperandsShape::Value { value: *value }, + Instruction::ImportCall { + specifier, + options, + phase, + } => OperandsShape::SpecifierOptions { + specifier: *specifier, + options: *options, + phase: *phase, }, Instruction::PushClassField { object, name, value, is_anonymous_function, - } => Operands::ClassField { - object: **object, - name: **name, - value: **value, - is_anonymous_function: **is_anonymous_function, + } => OperandsShape::ClassField { + object: *object, + name: *name, + value: *value, + is_anonymous_function: *is_anonymous_function, }, Instruction::MaybeException { has_exception, exception, - } => Operands::MaybeException { - has_exception: **has_exception, - exception: **exception, + } => OperandsShape::MaybeException { + has_exception: *has_exception, + exception: *exception, }, Instruction::SetAccumulator { src } | Instruction::PushFromRegister { src } @@ -774,77 +907,67 @@ impl Operands { | Instruction::ValueNotNullOrUndefined { src } | Instruction::GeneratorYield { src } | Instruction::AsyncGeneratorYield { src } - | Instruction::Await { src } => Operands::Src { - src: u32::from(*src), - }, + | Instruction::Await { src } => OperandsShape::Src { src: *src }, Instruction::IteratorPush { iterator, next } - | Instruction::IteratorPop { iterator, next } => Operands::IteratorNextReg { - iterator: **iterator, - next: **next, + | Instruction::IteratorPop { iterator, next } => OperandsShape::IteratorNextReg { + iterator: *iterator, + next: *next, }, - Instruction::IteratorUpdateResult { result } => Operands::Result { result: **result }, - Instruction::IteratorDone { dst } - | Instruction::IteratorValue { dst } + Instruction::IteratorUpdateResult { result } => { + OperandsShape::Result { result: *result } + } + Instruction::SetRegisterFromAccumulator { dst } + | Instruction::PopIntoRegister { dst } + | Instruction::StoreZero { dst } + | Instruction::StoreOne { dst } + | Instruction::StoreNan { dst } + | Instruction::StorePositiveInfinity { dst } + | Instruction::StoreNegativeInfinity { dst } + | Instruction::StoreNull { dst } + | Instruction::StoreTrue { dst } + | Instruction::StoreFalse { dst } + | Instruction::StoreUndefined { dst } + | Instruction::Exception { dst } + | Instruction::This { dst } + | Instruction::NewTarget { dst } + | Instruction::ImportMeta { dst } + | Instruction::CreateMappedArgumentsObject { dst } + | Instruction::CreateUnmappedArgumentsObject { dst } + | Instruction::RestParameterInit { dst } + | Instruction::StoreEmptyObject { dst } + | Instruction::IteratorDone { dst } | Instruction::IteratorResult { dst } - | Instruction::IteratorToArray { dst } | Instruction::IteratorStackEmpty { dst } - | Instruction::PushEmptyObject { dst } => Operands::Dst { dst: **dst }, - Instruction::IteratorFinishAsyncNext { resume_kind, value } => { - Operands::ResumeKindValue { - resume_kind: **resume_kind, - value: **value, - } - } - Instruction::IteratorReturn { value, called } => Operands::ValueCalled { - value: **value, - called: **called, - }, - Instruction::CreateGlobalFunctionBinding { - src, - configurable, - name_index, - } => Operands::SrcConfigurableName { - src: **src, - configurable: **configurable, - name_index: **name_index, - }, - Instruction::CreateGlobalVarBinding { - configurable, - name_index, - } => Operands::ConfigurableName { - configurable: u32::from(*configurable) == 1, - name_index: **name_index, - }, + | Instruction::IteratorValue { dst } + | Instruction::StoreNewArray { dst } => OperandsShape::Dst { dst: *dst }, Instruction::PushPrivateEnvironment { class, name_indices, - } => Operands::ClassNames { - class: **class, + } => OperandsShape::ClassNames { + class: *class, name_indices: name_indices .iter() .copied() .collect::>() .into_boxed_slice(), }, - Instruction::TemplateLookup { address, site, dst } => Operands::AddressSiteDst { - address: u32::from(*address), + Instruction::TemplateLookup { address, site, dst } => OperandsShape::AddressSiteDst { + address: *address, site: *site, - dst: **dst, + dst: *dst, }, - Instruction::JumpTable { index, addresses } => Operands::JumpTable { + Instruction::JumpTable { index, addresses } => OperandsShape::JumpTable { index: *index, addresses: addresses .iter() .copied() - .map(u32::from) .collect::>() .into_boxed_slice(), }, - Instruction::ConcatToString { dst, values } => Operands::DstValues { - dst: **dst, + Instruction::ConcatToString { dst, values } => OperandsShape::DstValues { + dst: *dst, values: values .iter() - .map(std::ops::Deref::deref) .copied() .collect::>() .into_boxed_slice(), @@ -853,28 +976,37 @@ impl Operands { object, source, excluded_keys, - } => Operands::ObjectSourceExcluded { - object: **object, - source: **source, + } => OperandsShape::ObjectSourceExcluded { + object: *object, + source: *source, excluded_keys: excluded_keys .iter() - .map(std::ops::Deref::deref) .copied() - .collect::>() + .collect::>() .into_boxed_slice(), }, - Instruction::TemplateCreate { site, dst, values } => Operands::SiteDstValues { + Instruction::TemplateCreate { site, dst, values } => OperandsShape::SiteDstValues { site: *site, - dst: **dst, + dst: *dst, values: values .iter() .copied() .collect::>() .into_boxed_slice(), }, - Instruction::GetFunctionObject { function_object } => Operands::FunctionObject { - function_object: **function_object, + Instruction::GetFunctionObject { function_object } => OperandsShape::FunctionObject { + function_object: *function_object, + }, + Instruction::StoreRegexp { + dst, + pattern_index, + flags_index, + } => OperandsShape::Regexp { + pattern_index: *pattern_index, + flags_index: *flags_index, + dst: *dst, }, + Instruction::Reserved1 | Instruction::Reserved2 | Instruction::Reserved3 @@ -928,12 +1060,19 @@ impl Operands { | Instruction::Reserved51 | Instruction::Reserved52 | Instruction::Reserved53 - | Instruction::Reserved54 => unreachable!("Reserved opcodes are unreachable"), + | Instruction::Reserved54 + | Instruction::Reserved55 + | Instruction::Reserved56 + | Instruction::Reserved57 + | Instruction::Reserved58 + | Instruction::Reserved59 + | Instruction::Reserved60 + | Instruction::Reserved61 => unreachable!("Reserved opcodes are unreachable"), } } } -impl std::fmt::Display for Operands { +impl std::fmt::Display for OperandsShape { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::None => Ok(()), @@ -955,7 +1094,11 @@ impl std::fmt::Display for Operands { }; write!(f, "function:{function}, name:{name}, {prefix_str}") } - Self::ValueDst { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::ValueDstI8 { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::ValueDstI16 { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::ValueDstI32 { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::ValueDstF32 { value, dst } => write!(f, "value:{value}, dst:{dst}"), + Self::ValueDstF64 { value, dst } => write!(f, "value:{value}, dst:{dst}"), Self::IndexDst { index, dst } => write!(f, "index:{index}, dst:{dst}"), Self::Message { message } => write!(f, "message:{message}"), Self::Regexp { @@ -1116,8 +1259,15 @@ impl std::fmt::Display for Operands { Self::ValueArray { value, array } => write!(f, "value:{value}, array:{array}"), Self::Array { array } => write!(f, "array:{array}"), Self::Value { value } => write!(f, "value:{value}"), - Self::SpecifierOptions { specifier, options } => { - write!(f, "specifier:{specifier}, options:{options}") + Self::SpecifierOptions { + specifier, + options, + phase, + } => { + write!( + f, + "specifier:{specifier}, options:{options}, options:{phase}" + ) } Self::ClassField { object, diff --git a/core/engine/src/vm/trace.rs b/core/engine/src/vm/trace.rs index 4bb16a70317..e06817d4edb 100644 --- a/core/engine/src/vm/trace.rs +++ b/core/engine/src/vm/trace.rs @@ -1,9 +1,10 @@ use std::time::Duration; -use super::{Vm, operands::Operands}; +use super::{Vm, operands::OperandsShape}; -use crate::{JsValue, vm::Opcode}; +use crate::JsValue; +/// A stack group represents a group of stack values based on the call frame. struct StackGroup { value: String, count: usize, @@ -20,23 +21,30 @@ impl StackGroup { } } -#[derive(Debug, Clone)] +/// Information about the current call frame. +#[derive(Debug, Clone, Copy)] pub struct CallFrameInfo { + /// The amount of call frames on the frame stack pub frame_count: usize, + /// The current frame pointer pub frame_pointer: usize, } -#[derive(Debug, Clone)] +/// Display options for the current stack trace +#[derive(Debug, Clone, Copy)] pub struct VmDisplayOptions { max_stack_width: usize, max_value_len: usize, } -/// A snapshot of the current stack at any moment in time +/// A trace of the current stack at a specific moment #[derive(Debug, Clone)] pub struct VmStackTrace { + /// A clone of the full stack pub stack: Vec, + /// Call frame information pub call_frame_info: CallFrameInfo, + /// Display options for the stack trace pub display_options: VmDisplayOptions, } @@ -44,7 +52,8 @@ impl VmStackTrace { const DEFAULT_MAX_VALUE_LEN: usize = 18; const DEFAULT_MAX_STACK_WIDTH: usize = 68; - pub fn new(vm: &Vm) -> Self { + /// Creates a new stack trace from the current Vm. + pub(crate) fn new(vm: &Vm) -> Self { let display_options = VmDisplayOptions { max_stack_width: Self::DEFAULT_MAX_STACK_WIDTH, max_value_len: Self::DEFAULT_MAX_VALUE_LEN, @@ -175,28 +184,36 @@ fn truncate_to_len(val: &str, max_len: usize) -> String { /// the global call frame. #[derive(Debug, Clone)] pub enum CallFrameName { + /// The global call frame Global, + /// The name of the current call frame. Name(String), } /// A message that is emitted at the beginning of execution #[derive(Debug, Clone)] pub struct ExecutionStartMessage { + /// The call frame name for the current execution start pub call_frame_name: CallFrameName, } /// A message that emits details about a call frame #[derive(Debug, Clone)] pub struct CallFrameMessage { + /// The displayable bytecode for the current call frame. pub bytecode: String, } /// A message that emits instruction execution details about a call frame #[derive(Debug, Clone)] pub struct OpcodeExecutionMessage { - pub opcode: Opcode, + /// The current opcode being executed + pub opcode: &'static str, + /// The duration taken for the current opcode execution pub duration: Duration, - pub operands: Operands, + /// The operands for the opcode + pub operands: OperandsShape, + /// A stack trace for the current execution pub stack_trace: VmStackTrace, } @@ -282,8 +299,6 @@ impl VirtualMachineTracer for StdoutTracer { stack_trace, } = execution_message; - let opcode = opcode.as_str(); - println!( "{:>, + } + + impl SnapshotTracer { + fn new(inner: Rc>) -> Self { + Self { inner } + } + } + + impl VirtualMachineTracer for SnapshotTracer { + fn emit_event(&self, event: VirtualMachineEvent) { + match event { + VirtualMachineEvent::CallFrameTrace(call_frame_message) => { + let mut out = self.inner.borrow_mut(); + writeln!(&mut *out, "{}", call_frame_message.bytecode).unwrap(); + } + _ => {} // Execution trace is a no-op for bytecode snapshotting + } + } + } glob!("../scripts/", "**/*.js", |path| { + let trace_sink = Rc::new(RefCell::new(String::new())); let context = &mut Context::default(); + context.set_trace(true); + context.set_virtual_machine_tracer(Box::new(SnapshotTracer::new(trace_sink.clone()))); let source = Source::from_filepath(path).expect("Could not load source"); - let script = Script::parse(source, None, context).unwrap(); - let output = script.codeblock(context).unwrap().to_string(); - insta::assert_snapshot!(output); + let result = match context.eval(source) { + Ok(v) => v.display().to_string(), + Err(e) => format!("{e}"), + }; + { + let mut sink = trace_sink.borrow_mut(); + writeln!(&mut sink, "Evaluation result: {result}").unwrap(); + } + insta::assert_snapshot!(*trace_sink.borrow()); }); } diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@basic-loop.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@basic-loop.js.snap index 3968ac2f83d..71bb0e3098e 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@basic-loop.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@basic-loop.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/basic-loop.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -21,3 +21,5 @@ Bindings: Handlers: Source Map: 0000: 16..25: (1, 26) + +Evaluation result: undefined diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@double-loop-function.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@double-loop-function.js.snap index 614fef957c3..2e4037df046 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@double-loop-function.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@double-loop-function.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/double-loop-function.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -42,3 +42,25 @@ Source Map: 0000: 16..55: (5, 25) 0001: 55..101: (6, 27) 0002: 101..112: (7, 6) + +----------------------------- Compiled Output: 'f' ----------------------------- +Location Handler Opcode Operands + 000000 GetArgument index:0, dst:r01 + 000009 Move src:r01, dst:r02 + 000012 Mul lhs:r02, rhs:r02, dst:r01 + 00001f PushFromRegister src:r01 + 000024 PopIntoRegister dst:r01 + 000029 SetAccumulator src:r01 + 00002e CheckReturn + 00002f Return + 000030 CheckReturn + 000031 Return + +Register Count: 3, Flags: CodeBlockFlags(HAS_PROTOTYPE_PROPERTY) +Constants: +Bindings: +Handlers: +Source Map: + 0000: 18..48: (1, 15) + +Evaluation result: undefined diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@generator-yield-star.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@generator-yield-star.js.snap index 8caf9b16070..c1fd2378970 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@generator-yield-star.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@generator-yield-star.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/generator-yield-star.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -8,10 +8,10 @@ Location Handler Opcode Operands 000000 GetLocator binding_index:0 000005 StoreEmptyObject dst:r01 00000a GetNameGlobal dst:r03, binding_index:1, ic_index:0 - 000017 GetPropertyByName dst:r02, value:r03, ic:(name:asyncIterator entries:()) + 000017 GetPropertyByName dst:r02, value:r03, ic:1 000024 ToPropertyKey src:r02, dst:r02 00002d GetFunction index:4, dst:r03 - 000036 SetFunctionName function:r03, name:r02, prefix:prefix: + 000036 SetFunctionName function:r03, name:r02, prefix: 000043 SetHomeObject function:r03, home:r01 00004c DefineOwnPropertyByValue object:r01, key:r02, value:r03 000059 SetNameByLocator src:r01 @@ -31,3 +31,198 @@ Bindings: Handlers: Source Map: 0000: 10..36: (2, 11) + +---------------------------- Compiled Output: 'gen' ---------------------------- +Location Handler Opcode Operands + 000000 AsyncGenerator + 000001 Pop + 000002 > 0: 000367 GetNameGlobal dst:r05, binding_index:0, ic_index:0 + 00000f 0: 000367 GetAsyncIterator src:r05 + 000014 0: 000367 StoreUndefined dst:r05 + 000019 0: 000367 StoreZero dst:r06 + 00001e 0: 000367 IteratorPop iterator:r08, next:r09 + 000027 0: 000367 JumpTable index:6, jump_table:(000076, 000099) + 000038 0: 000367 Move src:r08, dst:r07 + 000041 0: 000367 GetMethod object:r07, name_index:0 + 00004a 0: 000367 JumpIfNullOrUndefined value:r07, address:0001d5 + 000053 0: 000367 PushFromRegister src:r08 + 000058 0: 000367 PushFromRegister src:r07 + 00005d 0: 000367 PushFromRegister src:r05 + 000062 0: 000367 Call argument_count:1 + 000067 0: 000367 StoreTrue dst:r07 + 00006c 0: 000367 PopIntoRegister dst:r05 + 000071 0: 000367 Jump address:0000e0 + 000076 0: 000367 PushFromRegister src:r08 + 00007b 0: 000367 PushFromRegister src:r09 + 000080 0: 000367 PushFromRegister src:r05 + 000085 0: 000367 Call argument_count:1 + 00008a 0: 000367 StoreFalse dst:r07 + 00008f 0: 000367 PopIntoRegister dst:r05 + 000094 0: 000367 Jump address:0000e0 + 000099 0: 000367 Move src:r08, dst:r07 + 0000a2 0: 000367 GetMethod object:r07, name_index:1 + 0000ab 0: 000367 JumpIfNotUndefined value:r07, address:0000c2 + 0000b4 0: 000367 IteratorPush iterator:r08, next:r09 + 0000bd 0: 000367 Jump address:0002ae + 0000c2 0: 000367 PushFromRegister src:r08 + 0000c7 0: 000367 PushFromRegister src:r07 + 0000cc 0: 000367 PushFromRegister src:r05 + 0000d1 0: 000367 Call argument_count:1 + 0000d6 0: 000367 StoreFalse dst:r07 + 0000db 0: 000367 PopIntoRegister dst:r05 + 0000e0 0: 000367 IteratorPush iterator:r08, next:r09 + 0000e9 0: 000367 Await src:r05 + 0000ee 0: 000367 PopIntoRegister dst:r06 + 0000f3 0: 000367 PopIntoRegister dst:r05 + 0000f8 0: 000367 StoreOne dst:r08 + 0000fd 0: 000367 JumpIfNotEqual lhs:r08, rhs:r06, address:000118 + 00010a 0: 000367 IteratorPop iterator:r06, next:r06 + 000113 0: 000367 Throw src:r05 + 000118 0: 000367 IteratorUpdateResult result:r05 + 00011d 0: 000367 JumpIfFalse value:r05, address:000162 + 000126 0: 000367 IteratorValue dst:r05 + 00012b 0: 000367 MaybeException has_exception:r09, exception:r08 + 000134 0: 000367 JumpIfFalse value:r09, address:00014b + 00013d 0: 000367 IteratorPop iterator:r09, next:r09 + 000146 0: 000367 Throw src:r08 + 00014b 0: 000367 IteratorPop iterator:r06, next:r06 + 000154 0: 000367 JumpIfTrue value:r07, address:0001d5 + 00015d 0: 000367 Jump address:000367 + 000162 0: 000367 IteratorValue dst:r05 + 000167 0: 000367 MaybeException has_exception:r09, exception:r08 + 000170 0: 000367 JumpIfFalse value:r09, address:000187 + 000179 0: 000367 IteratorPop iterator:r09, next:r09 + 000182 0: 000367 Throw src:r08 + 000187 0: 000367 AsyncGeneratorYield src:r05 + 00018c 0: 000367 PopIntoRegister dst:r06 + 000191 0: 000367 PopIntoRegister dst:r05 + 000196 0: 000367 StoreInt8 value:2, dst:r08 + 00019c 0: 000367 JumpIfNotEqual lhs:r08, rhs:r06, address:0001d0 + 0001a9 0: 000367 Await src:r05 + 0001ae 0: 000367 PopIntoRegister dst:r06 + 0001b3 0: 000367 PopIntoRegister dst:r05 + 0001b8 0: 000367 StoreZero dst:r08 + 0001bd 0: 000367 JumpIfNotEqual lhs:r08, rhs:r06, address:0001d0 + 0001ca 0: 000367 StoreInt8 value:2, dst:r06 + 0001d0 0: 000367 Jump address:00001e + 0001d5 0: 000367 Await src:r05 + 0001da 0: 000367 Pop + 0001db 0: 000367 IteratorStackEmpty dst:r06 + 0001e0 0: 000367 JumpIfTrue value:r06, address:0002a2 + 0001e9 0: 000367 StoreFalse dst:r07 + 0001ee 0: 000367 IteratorStackEmpty dst:r08 + 0001f3 0: 000367 JumpIfTrue value:r08, address:000251 + 0001fc 0: 000367 IteratorDone dst:r08 + 000201 0: 000367 IteratorPop iterator:r06, next:r09 + 00020a 0: 000367 JumpIfTrue value:r08, address:000251 + 000213 0: 000367 Move src:r06, dst:r08 + 00021c 0: 000367 GetMethod object:r08, name_index:0 + 000225 0: 000367 JumpIfNullOrUndefined value:r08, address:000251 + 00022e 0: 000367 PushFromRegister src:r06 + 000233 0: 000367 PushFromRegister src:r08 + 000238 0: 000367 SetRegisterFromAccumulator dst:r06 + 00023d 0: 000367 Call argument_count:0 + 000242 0: 000367 SetAccumulator src:r06 + 000247 0: 000367 PopIntoRegister dst:r06 + 00024c 0: 000367 StoreTrue dst:r07 + 000251 0: 000367 JumpIfFalse value:r07, address:00029d + 00025a 0: 000367 Await src:r06 + 00025f 0: 000367 PopIntoRegister dst:r07 + 000264 0: 000367 PopIntoRegister dst:r06 + 000269 0: 000367 JumpTable index:7, jump_table:(000280, 000285) + 00027a 0: 000367 SetAccumulator src:r06 + 00027f 0: 000367 ReThrow + 000280 0: 000367 Jump address:00028a + 000285 0: 000367 Throw src:r06 + 00028a 0: 000367 IsObject value:r06 + 00028f 0: 000367 JumpIfTrue value:r06, address:00029d + 000298 0: 000367 ThrowNewTypeError message:2 + 00029d 0: 000367 Jump address:0001db + 0002a2 0: 000367 PopIntoRegister dst:r06 + 0002a7 0: 000367 SetAccumulator src:r06 + 0002ac 0: 000367 AsyncGeneratorClose + 0002ad 0: 000367 Return + 0002ae 0: 000367 StoreFalse dst:r07 + 0002b3 0: 000367 IteratorStackEmpty dst:r08 + 0002b8 0: 000367 JumpIfTrue value:r08, address:000316 + 0002c1 0: 000367 IteratorDone dst:r08 + 0002c6 0: 000367 IteratorPop iterator:r06, next:r09 + 0002cf 0: 000367 JumpIfTrue value:r08, address:000316 + 0002d8 0: 000367 Move src:r06, dst:r08 + 0002e1 0: 000367 GetMethod object:r08, name_index:0 + 0002ea 0: 000367 JumpIfNullOrUndefined value:r08, address:000316 + 0002f3 0: 000367 PushFromRegister src:r06 + 0002f8 0: 000367 PushFromRegister src:r08 + 0002fd 0: 000367 SetRegisterFromAccumulator dst:r06 + 000302 0: 000367 Call argument_count:0 + 000307 0: 000367 SetAccumulator src:r06 + 00030c 0: 000367 PopIntoRegister dst:r06 + 000311 0: 000367 StoreTrue dst:r07 + 000316 0: 000367 JumpIfFalse value:r07, address:000362 + 00031f 0: 000367 Await src:r06 + 000324 0: 000367 PopIntoRegister dst:r07 + 000329 0: 000367 PopIntoRegister dst:r06 + 00032e 0: 000367 JumpTable index:7, jump_table:(000345, 00034a) + 00033f 0: 000367 SetAccumulator src:r06 + 000344 0: 000367 ReThrow + 000345 0: 000367 Jump address:00034f + 00034a 0: 000367 Throw src:r06 + 00034f 0: 000367 IsObject value:r06 + 000354 0: 000367 JumpIfTrue value:r06, address:000362 + 00035d 0: 000367 ThrowNewTypeError message:2 + 000362 < 0: 000367 ThrowNewTypeError message:3 + 000367 AsyncGeneratorClose + 000368 Return + +Register Count: 10, Flags: CodeBlockFlags(IS_ASYNC | IS_GENERATOR) +Constants: + 0000: [STRING] "return" + 0001: [STRING] "throw" + 0002: [STRING] "inner result was not an object" + 0003: [STRING] "iterator does not have a throw method" +Bindings: + 0000: obj, scope: GlobalObject +Handlers: + 0000: Range: [000002, 000367): Handler: 000367, Environment: 00 +Source Map: + 0000: 2..871: (11, 23) + +-------------------------- Compiled Output: [anon#8] --------------------------- +Location Handler Opcode Operands + 000000 StoreEmptyObject dst:r01 + 000005 GetFunction index:0, dst:r02 + 00000e SetHomeObject function:r02, home:r01 + 000017 DefineOwnPropertyByName object:r01, value:r02, name_index:1 + 000024 PushFromRegister src:r01 + 000029 PopIntoRegister dst:r01 + 00002e SetAccumulator src:r01 + 000033 CheckReturn + 000034 Return + 000035 CheckReturn + 000036 Return + +Register Count: 3, Flags: CodeBlockFlags(0x0) +Constants: + 0000: [FUNCTION] name: 'next' (length: 0) + 0001: [STRING] "next" +Bindings: +Handlers: +Source Map: + 0000: 0..53: (2, 28) + +--------------------------- Compiled Output: 'next' ---------------------------- +Location Handler Opcode Operands + 000000 GetNameGlobal dst:r01, binding_index:0, ic_index:0 + 00000d Throw src:r01 + 000012 CheckReturn + 000013 Return + +Register Count: 2, Flags: CodeBlockFlags(0x0) +Constants: +Bindings: + 0000: reason, scope: GlobalObject +Handlers: +Source Map: + 0000: 0..18: (5, 15) + +Evaluation result: undefined diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@if-ternary-branch.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@if-ternary-branch.js.snap index f61dd2e97fc..465a699b7ff 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@if-ternary-branch.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@if-ternary-branch.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/if-ternary-branch.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -51,3 +51,5 @@ Source Map: 0001: 93..108: (16, 3) 0002: 108..183: (21, 1) 0003: 183..198: (25, 3) + +Evaluation result: 3 diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@loop-hoisting.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@loop-hoisting.js.snap index 15ecdb79d0c..5f5155b934e 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@loop-hoisting.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@loop-hoisting.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/loop-hoisting.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -61,3 +61,49 @@ Source Map: 0001: 78..153: (5, 24) 0002: 153..197: (14, 4) 0003: 197..207: (23, 4) + +---------------------------- Compiled Output: 'bar' ---------------------------- +Location Handler Opcode Operands + 000000 StoreZero dst:r02 + 000005 GetName dst:r03, binding_index:0 + 00000e Jump address:00001c + 000013 Inc src:r02, dst:r02 + 00001c JumpIfNotLessThan lhs:r02, rhs:r03, address:00002f + 000029 IncrementLoopIteration + 00002a Jump address:000013 + 00002f CheckReturn + 000030 Return + +Register Count: 5, Flags: CodeBlockFlags(HAS_PROTOTYPE_PROPERTY) +Constants: +Bindings: + 0000: z, scope: GlobalDeclarative +Handlers: +Source Map: + 0000: 0..19: (10, 16) + 0001: 19..28: (11, 26) + 0002: 28..47: (10, 16) + +---------------------------- Compiled Output: 'foo' ---------------------------- +Location Handler Opcode Operands + 000000 StoreZero dst:r02 + 000005 Jump address:000013 + 00000a Inc src:r02, dst:r02 + 000013 GetName dst:r03, binding_index:0 + 00001c JumpIfNotLessThan lhs:r02, rhs:r03, address:00002f + 000029 IncrementLoopIteration + 00002a Jump address:00000a + 00002f CheckReturn + 000030 Return + +Register Count: 4, Flags: CodeBlockFlags(HAS_PROTOTYPE_PROPERTY) +Constants: +Bindings: + 0000: x, scope: GlobalDeclarative +Handlers: +Source Map: + 0000: 0..10: (19, 16) + 0001: 10..19: (20, 26) + 0002: 19..47: (19, 16) + +Evaluation result: undefined diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@new.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@new.js.snap index dfe0745ed8a..a01ccca722a 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@new.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@new.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/new.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -22,7 +22,7 @@ Location Handler Opcode Operands 000062 PushFromRegister src:r06 000067 New argument_count:1 00006c PopIntoRegister dst:r04 - 000071 GetPropertyByName dst:r05, value:r04, ic:(name:map entries:()) + 000071 GetPropertyByName dst:r05, value:r04, ic:1 00007e PushFromRegister src:r04 000083 PushFromRegister src:r05 000088 GetFunction index:4, dst:r04 @@ -31,7 +31,7 @@ Location Handler Opcode Operands 00009b PopIntoRegister dst:r03 0000a0 GetIterator src:r03 0000a5 PushIteratorToArray array:r02 - 0000aa GetLengthProperty dst:r01, value:r02, ic:(name:length entries:()) + 0000aa GetLengthProperty dst:r01, value:r02, ic:2 0000b7 SetAccumulator src:r01 0000bc CheckReturn 0000bd Return @@ -55,3 +55,40 @@ Source Map: 0002: 113..136: (3, 52) 0003: 136..160: (3, 27) 0004: 160..183: (3, 52) + +------------------------- Compiled Output: 'SomeClass' ------------------------- +Location Handler Opcode Operands + 000000 StoreUndefined dst:r01 + 000005 SetAccumulator src:r01 + 00000a CheckReturn + 00000b Return + +Register Count: 2, Flags: CodeBlockFlags(STRICT | IS_CLASS_CONSTRUCTOR | HAS_PROTOTYPE_PROPERTY | HAS_FUNCTION_SCOPE) +Constants: + 0000: [SCOPE] index: 2, bindings: 0 +Bindings: +Handlers: +Source Map: + +-------------------------- Compiled Output: [anon#18] -------------------------- +Location Handler Opcode Operands + 000000 GetName dst:r01, binding_index:0 + 000009 PushFromRegister src:r00 + 00000e PushFromRegister src:r01 + 000013 New argument_count:0 + 000018 PopIntoRegister dst:r01 + 00001d SetAccumulator src:r01 + 000022 CheckReturn + 000023 Return + 000024 CheckReturn + 000025 Return + +Register Count: 2, Flags: CodeBlockFlags(0x0) +Constants: +Bindings: + 0000: SomeClass, scope: GlobalDeclarative +Handlers: +Source Map: + 0000: 0..36: (3, 34) + +Evaluation result: 100000 diff --git a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@try-finally.js.snap b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@try-finally.js.snap index 15c83270be2..824cc61a946 100644 --- a/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@try-finally.js.snap +++ b/tests/insta-bytecode/src/snapshots/insta_bytecode__compile_bytecode@try-finally.js.snap @@ -1,6 +1,6 @@ --- source: tests/insta-bytecode/src/lib.rs -expression: output +expression: "*trace_sink.borrow()" input_file: tests/insta-bytecode/scripts/try-finally.js --- -------------------------- Compiled Output: '
' --------------------------- @@ -68,3 +68,5 @@ Source Map: 0000: 30..190: (7, 24) 0001: 190..285: (12, 5) 0002: 285..322: (14, 3) + +Evaluation result: 310 From 5fd236b7da10f8ab7e4224d0dff835f94fe38e49 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 7 Sep 2026 15:10:27 -0500 Subject: [PATCH 4/6] Update flowgraph logic --- core/engine/src/vm/flowgraph/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 98f0d32dfbd..0d7c458174c 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -1,6 +1,6 @@ //! This module is responsible for generating the vm instruction flowgraph. -use crate::vm::CodeBlock; +use crate::vm::{CodeBlock, operands::OperandsShape}; mod color; mod edge; @@ -35,7 +35,7 @@ impl CodeBlock { while let Some((previous_pc, opcode, instruction)) = iterator.next() { let opcode_str = opcode.as_str(); - let label = format!("{opcode_str} {}", self.instruction_operands(&instruction)); + let label = format!("{opcode_str} {}", OperandsShape::from_instruction(&instruction)); let pc = iterator.pc(); From 809fc2b585d9e0b153a1e4f7f361ea9a85a88c01 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 7 Sep 2026 15:13:21 -0500 Subject: [PATCH 5/6] Fix clippy issue --- core/engine/src/vm/flowgraph/mod.rs | 5 ++++- tests/insta-bytecode/src/lib.rs | 9 +++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 0d7c458174c..565ff9aaec8 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -35,7 +35,10 @@ impl CodeBlock { while let Some((previous_pc, opcode, instruction)) = iterator.next() { let opcode_str = opcode.as_str(); - let label = format!("{opcode_str} {}", OperandsShape::from_instruction(&instruction)); + let label = format!( + "{opcode_str} {}", + OperandsShape::from_instruction(&instruction) + ); let pc = iterator.pc(); diff --git a/tests/insta-bytecode/src/lib.rs b/tests/insta-bytecode/src/lib.rs index 9c8e532d622..a0cd184dbea 100644 --- a/tests/insta-bytecode/src/lib.rs +++ b/tests/insta-bytecode/src/lib.rs @@ -19,12 +19,9 @@ fn compile_bytecode() { impl VirtualMachineTracer for SnapshotTracer { fn emit_event(&self, event: VirtualMachineEvent) { - match event { - VirtualMachineEvent::CallFrameTrace(call_frame_message) => { - let mut out = self.inner.borrow_mut(); - writeln!(&mut *out, "{}", call_frame_message.bytecode).unwrap(); - } - _ => {} // Execution trace is a no-op for bytecode snapshotting + if let VirtualMachineEvent::CallFrameTrace(call_frame_message) = event { + let mut out = self.inner.borrow_mut(); + writeln!(&mut *out, "{}", call_frame_message.bytecode).unwrap(); } } } From fd0451c1c26945de73590a5cceac01cf5d7c4d06 Mon Sep 17 00:00:00 2001 From: Kevin Ness Date: Mon, 7 Sep 2026 15:19:04 -0500 Subject: [PATCH 6/6] Missed some feature flags --- core/engine/src/vm/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 70f1c0488ff..e54a6c092ae 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -878,6 +878,7 @@ impl Context { pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord { let mut runtime_budget: u32 = budget; + #[cfg(feature = "trace")] if self.vm.trace { self.trace_call_frame(); } @@ -915,6 +916,7 @@ impl Context { } pub(crate) fn run(&mut self) -> CompletionRecord { + #[cfg(feature = "trace")] if self.vm.trace { self.trace_call_frame(); }