diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index b6fcb5925b9..57b66e6b40b 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/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 ae209f7363b..d6b67536d39 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::{Bytecode, InstructionIterator}, + operands::{Address, OperandsShape}, source_info::{SourceInfo, SourceMap, SourcePath}, }; @@ -369,571 +369,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::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::StoreNewArray { dst } => format!("dst:{dst}"), - Instruction::ToInt32 { dst, src } => format!("dst:{dst}, src:{src}"), - 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::StoreInt8 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::StoreInt16 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::StoreInt32 { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::StoreFloat { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::StoreDouble { value, dst } => { - format!("value:{value}, dst:{dst}") - } - Instruction::StoreLiteral { index, dst } - | Instruction::ThisForObjectEnvironmentName { index, dst } - | Instruction::GetFunction { index, dst } - | Instruction::GetArgument { index, dst } => { - format!("index:{index}, dst:{dst}") - } - Instruction::ThrowNewTypeError { message } - | Instruction::ThrowNewReferenceError { message } => format!("message:{message}"), - Instruction::StoreRegexp { - 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::DefEvalVar { 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::StoreClassPrototype { - 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, - phase, - } => { - let phase_str = match u32::from(*phase) { - 0 => "evaluation", - 1 => "defer", - 2 => "source", - _ => "unknown", - }; - format!("specifier:{specifier}, options:{options}, phase:{phase_str}") - } - 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::IteratorStackEmpty { dst } - | Instruction::StoreEmptyObject { dst } => { - format!("dst:{dst}") - } - 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 - | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 - | Instruction::Reserved59 - | Instruction::Reserved60 - | Instruction::Reserved61 => unreachable!("Reserved opcodes are unreachable"), - } - } -} - impl Display for CodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = self.name(); @@ -956,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 = self.instruction_operands(&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/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 98f0d32dfbd..565ff9aaec8 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,10 @@ 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(); diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 5f6170b0e68..e54a6c092ae 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -19,9 +19,14 @@ use boa_gc::{Finalize, Gc, Trace, custom_trace}; use shadow_stack::ShadowStack; use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task}; +#[cfg(feature = "trace")] +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}; @@ -53,6 +58,13 @@ pub(crate) mod opcode; pub(crate) mod shadow_stack; pub(crate) mod source_info; +/// Operand types specific to Boa's virtual machine +pub mod operands; + +/// Boa's virtual machine tracing types and logic +#[cfg(feature = "trace")] +pub mod trace; + #[cfg(feature = "flowgraph")] pub mod flowgraph; @@ -98,8 +110,10 @@ pub struct Vm { #[cfg(feature = "trace")] pub(crate) trace: bool, + + /// A tracer registered to emit VM events #[cfg(feature = "trace")] - pub(crate) current_frame: Option<*const CallFrame>, + pub(crate) tracer: Box, } /// The stack holds the [`JsValue`]s for the calling convention and registers. @@ -273,104 +287,6 @@ impl Stack { let index = self.stack.len() - existing_argument_count; self.stack.splice(index..index, arguments.iter().cloned()); } - - #[cfg(feature = "trace")] - const MAX_VALUE_LEN: usize = 18; - #[cfg(feature = "trace")] - const MAX_STACK_WIDTH: usize = 68; - - #[cfg(feature = "trace")] - 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(), - } - } - - #[cfg(feature = "trace")] - fn truncate_display(val: &str) -> String { - if val.len() <= Self::MAX_VALUE_LEN { - return val.to_string(); - } - let mut end = Self::MAX_VALUE_LEN - 2; - while !val.is_char_boundary(end) && end > 0 { - end -= 1; - } - format!("{}..", &val[..end]) - } - - #[cfg(feature = "trace")] - fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String { - let total = self.stack.len(); - if total == 0 { - return "[ ]".to_string(); - } - - let mut groups: Vec<(String, usize, Option)> = Vec::new(); - let mut force_truncate = false; - - // 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 = frame.frame_pointer() == idx; - let raw = Self::raw_value(v); - - if !is_frame - && let Some(last) = groups.last_mut() - && last.0 == raw - && last.2.is_none() - { - last.1 += 1; - } else { - let marker = if is_frame { Some(frame_count) } else { None }; - groups.push((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 groups.len() > Self::MAX_STACK_WIDTH / 2 { - force_truncate = true; - break; - } - } - } - - let mut string = String::from("[ "); - let mut truncated = force_truncate; - let suffix = format!(".. ({total} total) ]"); - - for (i, (val, count, marker)) in groups.iter().enumerate() { - let display_val = Self::truncate_display(val); - let part = if *count > 1 { - format!("{display_val} (x{count})") - } else { - display_val - }; - - let separator = if let Some(fc) = marker { - format!(" |{fc}|") - } else if i + 1 < groups.len() { - ",".to_string() - } else { - String::new() - }; - - let addition = format!("{part}{separator} "); - if string.len() + addition.len() + suffix.len() > Self::MAX_STACK_WIDTH { - truncated = true; - break; - } - string.push_str(&addition); - } - - if truncated { - string.push_str(&suffix); - } else { - string.push(']'); - } - string - } } /// Active runnable in the current vm context. @@ -423,8 +339,10 @@ impl Vm { shadow_stack: ShadowStack::default(), #[cfg(feature = "trace")] trace: false, - #[cfg(feature = "trace")] - current_frame: None, + #[cfg(all(feature = "trace", not(feature = "trace-stdout")))] + tracer: Box::new(EmptyTracer), + #[cfg(feature = "trace-stdout")] + tracer: Box::new(StdoutTracer), } } @@ -675,49 +593,46 @@ 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 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::{CallFrameName, ExecutionStartMessage, VirtualMachineEvent}; let frame = self.vm.frame(); - let msg = if self.vm.frames.is_empty() { - " VM Start ".to_string() + self.walk_code_block(frame.code_block()); + let call_frame_name = if self.vm.frames.is_empty() { + CallFrameName::Global } else { - format!( - " Call Frame '{}'{} ", - frame.code_block().name().to_std_string_escaped(), - if frame.code_block().name().is_empty() { - format!(" [anon#{}]", frame.code_block().debug_id) - } else { - String::new() - } - ) + CallFrameName::Name(frame.code_block().name().to_std_string_escaped()) }; - - // Only print a functions compiled output if it has not been printed already - if !frame.code_block.traced.get() { - println!("{}", frame.code_block); - frame.code_block.traced.set(true); - } - println!( - "{msg:-^width$}", - width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 - ); - println!( - "{:( @@ -728,40 +643,50 @@ impl Context { where F: FnOnce(&mut Context, Opcode) -> ControlFlow, { - if self.vm.current_frame != Some(self.vm.frame()) { - println!(); - self.trace_call_frame(); - self.vm.current_frame = Some(self.vm.frame()); - } + use crate::vm::operands::OperandsShape; + 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 = OperandsShape::from_instruction(&instruction); let instant = Instant::now(); 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); - - println!( - "{: { + self.vm + .tracer + .emit_event(VirtualMachineEvent::ExecutionCallEvent); + } + _ => {} + } + + let stack_trace = VmStackTrace::new(&self.vm); + self.vm + .tracer + .emit_event(VirtualMachineEvent::ExecutionTrace( + OpcodeExecutionMessage { + opcode: opcode.as_str(), + duration, + operands, + stack_trace, + }, + )); result } } @@ -953,6 +878,11 @@ 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(); + } + while let Some(byte) = self .vm .frame() @@ -986,6 +916,11 @@ impl Context { } pub(crate) fn run(&mut self) -> CompletionRecord { + #[cfg(feature = "trace")] + 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 67990049f99..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,151 +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)] -/// A register operand is a register index used in bytecode instructions. -pub(crate) struct RegisterOperand(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)] -/// 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 new file mode 100644 index 00000000000..d1161436482 --- /dev/null +++ b/core/engine/src/vm/operands.rs @@ -0,0 +1,1348 @@ +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 OperandsShape { + None, + Dst { + dst: RegisterOperand, + }, + LhsRhsDst { + lhs: RegisterOperand, + rhs: RegisterOperand, + dst: RegisterOperand, + }, + RhsIndexDst { + rhs: RegisterOperand, + index: IndexOperand, + dst: RegisterOperand, + }, + SrcDst { + src: RegisterOperand, + dst: RegisterOperand, + }, + SetFunctionName { + function: RegisterOperand, + name: RegisterOperand, + prefix: u8, + }, + ValueDstI8 { + value: i8, + dst: RegisterOperand, + }, + ValueDstI16 { + value: i16, + dst: RegisterOperand, + }, + ValueDstI32 { + value: i32, + dst: RegisterOperand, + }, + ValueDstF32 { + value: f32, + dst: RegisterOperand, + }, + ValueDstF64 { + value: f64, + dst: RegisterOperand, + }, + IndexDst { + index: IndexOperand, + dst: RegisterOperand, + }, + Message { + message: IndexOperand, + }, + Regexp { + pattern_index: IndexOperand, + flags_index: IndexOperand, + dst: RegisterOperand, + }, + Address { + address: Address, + }, + AddressValue { + address: Address, + value: RegisterOperand, + }, + AddressLhsRhs { + address: Address, + lhs: RegisterOperand, + rhs: RegisterOperand, + }, + Case { + address: Address, + value: RegisterOperand, + condition: RegisterOperand, + }, + CallEval { + argument_count: IndexOperand, + scope_index: IndexOperand, + }, + ScopeIndex { + scope_index: IndexOperand, + }, + ArgumentCount { + argument_count: IndexOperand, + }, + BindingIndex { + binding_index: IndexOperand, + }, + SrcBindingIndex { + src: RegisterOperand, + binding_index: IndexOperand, + }, + DstBindingIndex { + dst: RegisterOperand, + binding_index: IndexOperand, + }, + GetNameGlobal { + dst: RegisterOperand, + binding_index: IndexOperand, + ic_index: IndexOperand, + }, + ObjectValueName { + object: RegisterOperand, + value: RegisterOperand, + name_index: IndexOperand, + }, + DstObjectName { + dst: RegisterOperand, + object: RegisterOperand, + name_index: IndexOperand, + }, + ObjectProtoValueName { + object: RegisterOperand, + proto: RegisterOperand, + value: RegisterOperand, + name_index: IndexOperand, + }, + Index { + index: IndexOperand, + }, + ObjectName { + object: RegisterOperand, + name_index: IndexOperand, + }, + DstValueIc { + dst: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, + }, + DstReceiverValueIc { + dst: RegisterOperand, + receiver: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, + }, + ObjectValueIc { + object: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, + }, + ObjectReceiverValueIc { + object: RegisterOperand, + receiver: RegisterOperand, + value: RegisterOperand, + ic_index: IndexOperand, + }, + DstKeyReceiverObject { + dst: RegisterOperand, + key: RegisterOperand, + receiver: RegisterOperand, + object: RegisterOperand, + }, + ObjectReceiverKeyValue { + object: RegisterOperand, + receiver: RegisterOperand, + key: RegisterOperand, + value: RegisterOperand, + }, + ObjectKeyValue { + object: RegisterOperand, + key: RegisterOperand, + value: RegisterOperand, + }, + ObjectKey { + object: RegisterOperand, + key: RegisterOperand, + }, + ValueDone { + value: RegisterOperand, + done: IndexOperand, + }, + DstClassSuperclass { + dst: RegisterOperand, + class: RegisterOperand, + superclass: RegisterOperand, + }, + DstPrototypeClass { + dst: RegisterOperand, + prototype: RegisterOperand, + class: RegisterOperand, + }, + FunctionHome { + function: RegisterOperand, + home: RegisterOperand, + }, + Function { + function: RegisterOperand, + }, + ObjectPrototype { + object: RegisterOperand, + prototype: RegisterOperand, + }, + Object { + object: RegisterOperand, + }, + ValueArray { + value: RegisterOperand, + array: RegisterOperand, + }, + Array { + array: RegisterOperand, + }, + Value { + value: RegisterOperand, + }, + SpecifierOptions { + specifier: RegisterOperand, + options: RegisterOperand, + phase: IndexOperand, + }, + ClassField { + object: RegisterOperand, + name: RegisterOperand, + value: RegisterOperand, + is_anonymous_function: IndexOperand, + }, + MaybeException { + has_exception: RegisterOperand, + exception: RegisterOperand, + }, + Src { + src: RegisterOperand, + }, + IteratorNextReg { + iterator: RegisterOperand, + next: RegisterOperand, + }, + Result { + result: RegisterOperand, + }, + ResumeKindValue { + resume_kind: RegisterOperand, + value: RegisterOperand, + }, + ValueCalled { + value: RegisterOperand, + called: RegisterOperand, + }, + SrcConfigurableName { + src: RegisterOperand, + configurable: RegisterOperand, + name_index: IndexOperand, + }, + ConfigurableName { + configurable: bool, + name_index: IndexOperand, + }, + ClassNames { + class: RegisterOperand, + name_indices: Box<[u32]>, + }, + AddressSiteDst { + address: Address, + site: u64, + dst: RegisterOperand, + }, + JumpTable { + index: u32, + addresses: Box<[Address]>, + }, + DstValues { + dst: RegisterOperand, + values: Box<[RegisterOperand]>, + }, + ObjectSourceExcluded { + object: RegisterOperand, + source: RegisterOperand, + excluded_keys: Box<[RegisterOperand]>, + }, + SiteDstValues { + site: u64, + dst: RegisterOperand, + values: Box<[u32]>, + }, + FunctionObject { + function_object: RegisterOperand, + }, +} + +impl OperandsShape { + pub(crate) 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 => OperandsShape::None, + + 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 } => OperandsShape::LhsRhsDst { + lhs: *lhs, + rhs: *rhs, + 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::ToInt32 { src, dst } + | Instruction::ToPropertyKey { src, dst } => OperandsShape::SrcDst { + src: *src, + dst: *dst, + }, + + Instruction::SetFunctionName { + function, + name, + prefix, + } => OperandsShape::SetFunctionName { + function: *function, + name: *name, + prefix: u32::from(*prefix) as u8, + }, + Instruction::ThisForObjectEnvironmentName { index, dst } + | Instruction::GetFunction { index, dst } + | Instruction::StoreLiteral { index, dst } + | Instruction::GetArgument { index, dst } => OperandsShape::IndexDst { + index: *index, + dst: *dst, + }, + + Instruction::ThrowNewTypeError { message } + | Instruction::ThrowNewReferenceError { message } => { + OperandsShape::Message { message: *message } + } + + 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::StoreClassPrototype { + dst, + class, + superclass, + } => OperandsShape::DstClassSuperclass { + dst: *dst, + class: *class, + superclass: *superclass, + }, + + 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 } => 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 } => OperandsShape::AddressLhsRhs { + address: *address, + lhs: *lhs, + rhs: *rhs, + }, + + Instruction::Case { + address, + value, + condition, + } => OperandsShape::Case { + address: *address, + value: *value, + condition: *condition, + }, + + Instruction::CallEval { + argument_count, + scope_index, + } => OperandsShape::CallEval { + argument_count: *argument_count, + scope_index: *scope_index, + }, + + Instruction::CallEvalSpread { scope_index } + | Instruction::PushScope { scope_index } => OperandsShape::ScopeIndex { + scope_index: *scope_index, + }, + + Instruction::Call { argument_count } + | Instruction::New { argument_count } + | Instruction::SuperCall { argument_count } => OperandsShape::ArgumentCount { + argument_count: *argument_count, + }, + + 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 } => 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 } => OperandsShape::DstBindingIndex { + dst: *dst, + binding_index: *binding_index, + }, + + Instruction::GetNameGlobal { + dst, + binding_index, + ic_index, + } => OperandsShape::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, + } => OperandsShape::ObjectValueName { + object: *object, + value: *value, + name_index: *name_index, + }, + Instruction::GetPrivateField { + dst, + object, + name_index, + } => OperandsShape::DstObjectName { + dst: *dst, + object: *object, + name_index: *name_index, + }, + Instruction::PushClassPrivateMethod { + object, + proto, + value, + name_index, + } => OperandsShape::ObjectProtoValueName { + object: *object, + proto: *proto, + value: *value, + name_index: *name_index, + }, + Instruction::ThrowMutateImmutable { index } => OperandsShape::Index { index: *index }, + Instruction::DeletePropertyByName { object, name_index } + | Instruction::GetMethod { object, name_index } => OperandsShape::ObjectName { + object: *object, + name_index: *name_index, + }, + Instruction::GetLengthProperty { + dst, + value, + ic_index, + } + | Instruction::GetPropertyByName { + dst, + value, + ic_index, + } => OperandsShape::DstValueIc { + dst: *dst, + value: *value, + ic_index: *ic_index, + }, + Instruction::GetPropertyByNameWithThis { + dst, + receiver, + value, + ic_index, + } => OperandsShape::DstReceiverValueIc { + dst: *dst, + receiver: *receiver, + value: *value, + ic_index: *ic_index, + }, + Instruction::SetPropertyByName { + value, + object, + ic_index, + } => OperandsShape::ObjectValueIc { + object: *object, + value: *value, + ic_index: *ic_index, + }, + Instruction::SetPropertyByNameWithThis { + value, + receiver, + object, + ic_index, + } => OperandsShape::ObjectReceiverValueIc { + object: *object, + receiver: *receiver, + value: *value, + ic_index: *ic_index, + }, + Instruction::GetPropertyByValue { + dst, + key, + receiver, + object, + } + | Instruction::GetPropertyByValuePush { + dst, + key, + receiver, + object, + } => OperandsShape::DstKeyReceiverObject { + dst: *dst, + key: *key, + receiver: *receiver, + object: *object, + }, + Instruction::SetPropertyByValue { + value, + key, + receiver, + object, + } => OperandsShape::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 } => { + OperandsShape::ObjectKeyValue { + object: *object, + key: *key, + value: *value, + } + } + Instruction::DeletePropertyByValue { key, object } => OperandsShape::ObjectKey { + object: *object, + key: *key, + }, + Instruction::CreateIteratorResult { value, done } => OperandsShape::ValueDone { + value: *value, + done: *done, + }, + Instruction::SetClassPrototype { + dst, + prototype, + class, + } => OperandsShape::DstPrototypeClass { + dst: *dst, + prototype: *prototype, + class: *class, + }, + Instruction::SetHomeObject { function, home } => OperandsShape::FunctionHome { + function: *function, + home: *home, + }, + Instruction::GetHomeObject { function } => OperandsShape::Function { + function: *function, + }, + Instruction::SetPrototype { object, prototype } => OperandsShape::ObjectPrototype { + object: *object, + prototype: *prototype, + }, + Instruction::GetPrototype { object } => OperandsShape::Object { object: *object }, + Instruction::PushValueToArray { value, array } => OperandsShape::ValueArray { + value: *value, + array: *array, + }, + Instruction::PushElisionToArray { 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 } => 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, + } => OperandsShape::ClassField { + object: *object, + name: *name, + value: *value, + is_anonymous_function: *is_anonymous_function, + }, + Instruction::MaybeException { + has_exception, + exception, + } => OperandsShape::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 } => OperandsShape::Src { src: *src }, + Instruction::IteratorPush { iterator, next } + | Instruction::IteratorPop { iterator, next } => OperandsShape::IteratorNextReg { + iterator: *iterator, + next: *next, + }, + 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::IteratorStackEmpty { dst } + | Instruction::IteratorValue { dst } + | Instruction::StoreNewArray { dst } => OperandsShape::Dst { dst: *dst }, + Instruction::PushPrivateEnvironment { + class, + name_indices, + } => OperandsShape::ClassNames { + class: *class, + name_indices: name_indices + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::TemplateLookup { address, site, dst } => OperandsShape::AddressSiteDst { + address: *address, + site: *site, + dst: *dst, + }, + Instruction::JumpTable { index, addresses } => OperandsShape::JumpTable { + index: *index, + addresses: addresses + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::ConcatToString { dst, values } => OperandsShape::DstValues { + dst: *dst, + values: values + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::CopyDataProperties { + object, + source, + excluded_keys, + } => OperandsShape::ObjectSourceExcluded { + object: *object, + source: *source, + excluded_keys: excluded_keys + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + Instruction::TemplateCreate { site, dst, values } => OperandsShape::SiteDstValues { + site: *site, + dst: *dst, + values: values + .iter() + .copied() + .collect::>() + .into_boxed_slice(), + }, + 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 + | 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 + | Instruction::Reserved55 + | Instruction::Reserved56 + | Instruction::Reserved57 + | Instruction::Reserved58 + | Instruction::Reserved59 + | Instruction::Reserved60 + | Instruction::Reserved61 => unreachable!("Reserved opcodes are unreachable"), + } + } +} + +impl std::fmt::Display for OperandsShape { + 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::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 { + 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, + phase, + } => { + write!( + f, + "specifier:{specifier}, options:{options}, options:{phase}" + ) + } + 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 new file mode 100644 index 00000000000..e06817d4edb --- /dev/null +++ b/core/engine/src/vm/trace.rs @@ -0,0 +1,312 @@ +use std::time::Duration; + +use super::{Vm, operands::OperandsShape}; + +use crate::JsValue; + +/// A stack group represents a group of stack values based on the call frame. +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, + } + } +} + +/// 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, +} + +/// Display options for the current stack trace +#[derive(Debug, Clone, Copy)] +pub struct VmDisplayOptions { + max_stack_width: usize, + max_value_len: usize, +} + +/// 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, +} + +impl VmStackTrace { + const DEFAULT_MAX_VALUE_LEN: usize = 18; + const DEFAULT_MAX_STACK_WIDTH: usize = 68; + + /// 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, + }; + + 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 +/// 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 { + /// The current opcode being executed + pub opcode: &'static str, + /// The duration taken for the current opcode execution + pub duration: Duration, + /// The operands for the opcode + pub operands: OperandsShape, + /// A stack trace for the current execution + pub stack_trace: VmStackTrace, +} + +/// The various events that are emitted from Boa's virtual machine. +#[derive(Debug, Clone)] +pub enum VirtualMachineEvent { + /// This event is the first event triggered. + /// + /// It emits information about the call frame. + CallFrameTrace(CallFrameMessage), + /// This event is triggered when the execution of a call frame is starting. + ExecutionStart(ExecutionStartMessage), + /// This event is triggered when executing an operation. + /// + /// It provides information about the opcode execution + ExecutionTrace(OpcodeExecutionMessage), + /// This event is triggered when a opcode that calls is reached. + /// + /// It signals that we about about to switch call frames. + ExecutionCallEvent, +} + +/// A trait to define a tracer that plugs into Boa's `Vm` +pub trait VirtualMachineTracer: std::fmt::Debug { + /// Emits `VirtualMachineEvent`s from the virtual machine during execution + fn emit_event(&self, _event: VirtualMachineEvent) {} +} + +/// A default empty virtual machine tracer that drops events submitted to it. +#[derive(Debug, Clone, Copy)] +pub struct EmptyTracer; + +impl VirtualMachineTracer for EmptyTracer {} + +/// `StdoutTracer` is a `VirtualMachineTracer` implementation that prints the events +/// to stdout in a specific format. +#[derive(Debug, Clone, Copy)] +pub struct StdoutTracer; + +impl StdoutTracer { + 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; +} + +#[allow(clippy::print_stdout)] +impl VirtualMachineTracer for StdoutTracer { + fn emit_event(&self, event: VirtualMachineEvent) { + match event { + VirtualMachineEvent::ExecutionStart(start_message) => { + 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_trace, + } = execution_message; + + println!( + "{:>, + } + + impl SnapshotTracer { + fn new(inner: Rc>) -> Self { + Self { inner } + } + } + + impl VirtualMachineTracer for SnapshotTracer { + fn emit_event(&self, event: VirtualMachineEvent) { + if let VirtualMachineEvent::CallFrameTrace(call_frame_message) = event { + let mut out = self.inner.borrow_mut(); + writeln!(&mut *out, "{}", call_frame_message.bytecode).unwrap(); + } + } + } 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