Skip to main content

boa_engine/vm/
mod.rs

1//! Boa's ECMAScript Virtual Machine
2//!
3//! The Virtual Machine (VM) handles generating instructions, then executing them.
4//! This module will provide an instruction set for the AST to use, various traits,
5//! plus an interpreter to execute those instructions
6
7use crate::{
8    Context, JsError, JsExpect, JsNativeError, JsObject, JsResult, JsString, JsValue, Module,
9    builtins::promise::{PromiseCapability, ResolvingFunctions},
10    environments::EnvironmentStack,
11    error::RuntimeLimitError,
12    object::JsFunction,
13    realm::Realm,
14    script::Script,
15    vm::opcode::{OPCODE_HANDLERS, OPCODE_HANDLERS_BUDGET},
16    vm::shadow_stack::ErrorStack,
17};
18use boa_gc::{Finalize, Gc, Trace, custom_trace};
19use shadow_stack::ShadowStack;
20use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task};
21
22#[cfg(feature = "trace")]
23use crate::sys::time::Instant;
24
25#[allow(unused_imports)]
26pub(crate) use opcode::{Instruction, InstructionIterator, Opcode};
27
28pub(crate) use {
29    call_frame::CallFrameFlags,
30    code_block::{
31        CodeBlockFlags, Constant, Handler, create_function_object, create_function_object_fast,
32    },
33    completion_record::CompletionRecord,
34    inline_cache::InlineCache,
35};
36
37pub use runtime_limits::RuntimeLimits;
38pub use {
39    call_frame::{CallFrame, GeneratorResumeKind},
40    code_block::CodeBlock,
41    source_info::{NativeSourceInfo, SourcePath},
42};
43
44pub(crate) use code_block::GlobalFunctionBinding;
45
46mod call_frame;
47mod code_block;
48mod completion_record;
49mod inline_cache;
50mod runtime_limits;
51
52pub(crate) mod opcode;
53pub(crate) mod shadow_stack;
54pub(crate) mod source_info;
55
56#[cfg(feature = "flowgraph")]
57pub mod flowgraph;
58
59#[cfg(test)]
60mod tests;
61
62/// Virtual Machine.
63#[derive(Debug)]
64pub struct Vm {
65    /// The call frame stack.
66    ///
67    /// The current frame is always the last element. A dummy frame is always
68    /// present at position 0 so the stack is never empty.
69    pub(crate) frames: Vec<CallFrame>,
70
71    pub(crate) stack: Stack,
72
73    pub(crate) return_value: JsValue,
74
75    /// When an error is thrown, the pending exception is set.
76    ///
77    /// If we throw an empty exception ([`None`]), this means that `return()` was called on a generator,
78    /// propagating though the exception handlers and executing the finally code (if any).
79    ///
80    /// See [`ReThrow`](crate::vm::Opcode::ReThrow) and [`ReThrow`](crate::vm::Opcode::Exception) opcodes.
81    ///
82    /// This eliminates the conversion between [`crate::JsNativeError`] and [`crate::JsValue`] if not needed.
83    pub(crate) pending_exception: Option<JsError>,
84    pub(crate) runtime_limits: RuntimeLimits,
85
86    /// This is used to assign a native (rust) function as the active function,
87    /// because we don't push a frame for them.
88    pub(crate) native_active_function: Option<JsObject>,
89
90    /// Number of nested host calls that re-enter the VM via `Context::run()`.
91    ///
92    /// This is incremented by high-level host entry points such as
93    /// [`JsObject::call`](crate::object::JsObject::call) and
94    /// [`JsObject::construct`](crate::object::JsObject::construct).
95    pub(crate) host_call_depth: usize,
96
97    pub(crate) shadow_stack: ShadowStack,
98
99    #[cfg(feature = "trace")]
100    pub(crate) trace: bool,
101    #[cfg(feature = "trace")]
102    pub(crate) current_frame: Option<*const CallFrame>,
103}
104
105/// The stack holds the [`JsValue`]s for the calling convention and registers.
106///
107/// The stack is persistent across frames.
108/// It's addressing is relative to the frame pointer (`fp`) in each [`CallFrame`].
109///
110/// The stack stores the following elements:
111/// - The function prologue
112///   - The `this` value of the function
113///   - The function object itself
114/// - The arguments of the function
115/// - The register file for the frame
116/// - Some manually pushed values like the return value of a function.
117///
118/// ```text
119///  Stack: | this | func | arg1 | ... | argN | reg0 | reg1 | ... | regK |
120///           ▲                                  ▲
121///           └─ fp                              └─ rp
122/// ```
123#[derive(Clone, Debug, Trace, Finalize)]
124pub(crate) struct Stack {
125    stack: Vec<JsValue>,
126}
127
128impl Stack {
129    /// Creates a new stack with the given capacity.
130    fn new(capacity: usize) -> Self {
131        Self {
132            stack: Vec::with_capacity(capacity),
133        }
134    }
135
136    /// Get a register value by index, relative to the given frame's `rp`.
137    pub(crate) fn get_register(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
138        self.stack.get(frame.rp as usize + index)
139    }
140
141    /// Set a register value by index, relative to the given frame's `rp`.
142    pub(crate) fn set_register(&mut self, frame: &CallFrame, index: usize, value: JsValue) {
143        self.stack[frame.rp as usize + index] = value;
144    }
145
146    /// Truncate the stack to the given frame.
147    pub(crate) fn truncate_to_frame(&mut self, frame: &CallFrame) {
148        self.stack.truncate(frame.frame_pointer());
149    }
150
151    /// Split the stack at the given frame.
152    pub(crate) fn split_off_frame(&mut self, frame: &CallFrame) -> Self {
153        let frame_pointer = frame.frame_pointer();
154        Self {
155            stack: self.stack.split_off(frame_pointer),
156        }
157    }
158
159    /// Get the `this` value of the given frame.
160    pub(crate) fn get_this(&self, frame: &CallFrame) -> JsValue {
161        self.stack[frame.this_index()].clone()
162    }
163
164    /// Set the `this` value of the given frame.
165    pub(crate) fn set_this(&mut self, frame: &CallFrame, this: JsValue) {
166        self.stack[frame.this_index()] = this;
167    }
168
169    /// Get the function object of the given frame.
170    pub(crate) fn get_function(&self, frame: &CallFrame) -> Option<JsObject> {
171        if let Some(object) = self.stack[frame.function_index()].as_object() {
172            return Some(object.clone());
173        }
174        None
175    }
176
177    /// Get the function arguments of the given frame.
178    pub(crate) fn get_arguments(&self, frame: &CallFrame) -> &[JsValue] {
179        &self.stack[frame.arguments_range()]
180    }
181
182    /// Get a single function argument of the given frame by index.
183    pub(crate) fn get_argument(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
184        self.get_arguments(frame).get(index)
185    }
186
187    /// Get the rest arguments of the given frame.
188    pub(crate) fn pop_rest_arguments(&mut self, frame: &CallFrame) -> Option<Vec<JsValue>> {
189        let argument_count = frame.argument_count as usize;
190        let param_count = frame.code_block().parameter_length as usize;
191        if argument_count < param_count {
192            return None;
193        }
194        let args_start = frame.fp as usize + CallFrame::FUNCTION_PROLOGUE as usize;
195        let args_end = args_start + argument_count;
196        let rest_count = argument_count - param_count + 1;
197
198        Some(
199            self.stack
200                .drain((args_end - rest_count)..args_end)
201                .collect(),
202        )
203    }
204
205    /// Push a value on the stack.
206    pub(crate) fn push<T>(&mut self, value: T)
207    where
208        T: Into<JsValue>,
209    {
210        self.stack.push(value.into());
211    }
212
213    /// Pop a value off the stack.
214    ///
215    /// # Panics
216    ///
217    /// If there is nothing to pop, then this will panic.
218    #[track_caller]
219    pub(crate) fn pop(&mut self) -> JsValue {
220        self.stack.pop().expect("stack was empty")
221    }
222
223    /// Pop the function arguments according to the calling convention.
224    /// This will pop the last `argument_count` values from the stack.
225    pub(crate) fn calling_convention_pop_arguments(
226        &mut self,
227        argument_count: usize,
228    ) -> Vec<JsValue> {
229        let index = self.stack.len() - argument_count;
230        self.stack.split_off(index)
231    }
232
233    /// Push the function arguments according to the calling convention.
234    /// This will push the given values onto the stack.
235    pub(crate) fn calling_convention_push_arguments(&mut self, values: &[JsValue]) {
236        self.stack.extend_from_slice(values);
237    }
238
239    /// Get the function object at the top of the stack according to the calling convention.
240    #[track_caller]
241    pub(crate) fn calling_convention_get_function(&self, argument_count: usize) -> &JsValue {
242        let index = self.stack.len() - 1 - argument_count;
243        self.stack
244            .get(index)
245            .expect("invalid calling convention function index")
246    }
247
248    /// Set the function object value at the top of the stack according to the calling convention.
249    #[track_caller]
250    pub(crate) fn calling_convention_set_function(
251        &mut self,
252        argument_count: usize,
253        function: JsValue,
254    ) {
255        let index = self.stack.len() - 1 - argument_count;
256        self.stack[index] = function;
257    }
258
259    /// Set the `this` value at the top of the stack according to the calling convention.
260    #[track_caller]
261    pub(crate) fn calling_convention_set_this(&mut self, argument_count: usize, function: JsValue) {
262        let index = self.stack.len() - 2 - argument_count;
263        self.stack[index] = function;
264    }
265
266    /// Insert the function arguments at the top of the stack according to the calling convention.
267    /// This will insert the given values at the position of the function arguments.
268    pub(crate) fn calling_convention_insert_arguments(
269        &mut self,
270        existing_argument_count: usize,
271        arguments: &[JsValue],
272    ) {
273        let index = self.stack.len() - existing_argument_count;
274        self.stack.splice(index..index, arguments.iter().cloned());
275    }
276
277    #[cfg(feature = "trace")]
278    const MAX_VALUE_LEN: usize = 18;
279    #[cfg(feature = "trace")]
280    const MAX_STACK_WIDTH: usize = 68;
281
282    #[cfg(feature = "trace")]
283    fn raw_value(value: &JsValue) -> String {
284        match value {
285            v if v.is_callable() => "func".to_string(),
286            v if v.is_object() => "obj".to_string(),
287            v if v.is_undefined() => "und".to_string(),
288            v if v.is_null() => "null".to_string(),
289            v => v.display().to_string(),
290        }
291    }
292
293    #[cfg(feature = "trace")]
294    fn truncate_display(val: &str) -> String {
295        if val.len() <= Self::MAX_VALUE_LEN {
296            return val.to_string();
297        }
298        let mut end = Self::MAX_VALUE_LEN - 2;
299        while !val.is_char_boundary(end) && end > 0 {
300            end -= 1;
301        }
302        format!("{}..", &val[..end])
303    }
304
305    #[cfg(feature = "trace")]
306    fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String {
307        let total = self.stack.len();
308        if total == 0 {
309            return "[ <empty> ]".to_string();
310        }
311
312        let mut groups: Vec<(String, usize, Option<usize>)> = Vec::new();
313        let mut force_truncate = false;
314
315        // Lazily group values to avoid eagerly evaluating `raw_value` for the entire stack.
316        for (idx, v) in self.stack.iter().enumerate().rev() {
317            let is_frame = frame.frame_pointer() == idx;
318            let raw = Self::raw_value(v);
319
320            if !is_frame
321                && let Some(last) = groups.last_mut()
322                && last.0 == raw
323                && last.2.is_none()
324            {
325                last.1 += 1;
326            } else {
327                let marker = if is_frame { Some(frame_count) } else { None };
328                groups.push((raw, 1, marker));
329
330                // If groups is large enough to mathematically guarantee overflowing the display width,
331                // we can stop evaluating to save instruction budget / time.
332                if groups.len() > Self::MAX_STACK_WIDTH / 2 {
333                    force_truncate = true;
334                    break;
335                }
336            }
337        }
338
339        let mut string = String::from("[ ");
340        let mut truncated = force_truncate;
341        let suffix = format!(".. ({total} total) ]");
342
343        for (i, (val, count, marker)) in groups.iter().enumerate() {
344            let display_val = Self::truncate_display(val);
345            let part = if *count > 1 {
346                format!("{display_val} (x{count})")
347            } else {
348                display_val
349            };
350
351            let separator = if let Some(fc) = marker {
352                format!(" |{fc}|")
353            } else if i + 1 < groups.len() {
354                ",".to_string()
355            } else {
356                String::new()
357            };
358
359            let addition = format!("{part}{separator} ");
360            if string.len() + addition.len() + suffix.len() > Self::MAX_STACK_WIDTH {
361                truncated = true;
362                break;
363            }
364            string.push_str(&addition);
365        }
366
367        if truncated {
368            string.push_str(&suffix);
369        } else {
370            string.push(']');
371        }
372        string
373    }
374}
375
376/// Active runnable in the current vm context.
377#[derive(Debug, Clone, Finalize)]
378pub enum ActiveRunnable {
379    /// A [**Script Record**](https://tc39.es/ecma262/#sec-script-records)
380    Script(Script),
381    /// A [**Source Text Module Record**](https://tc39.es/ecma262/#sec-source-text-module-records).
382    Module(Module),
383}
384
385unsafe impl Trace for ActiveRunnable {
386    custom_trace!(this, mark, {
387        match this {
388            Self::Script(script) => mark(script),
389            Self::Module(module) => mark(module),
390        }
391    });
392}
393
394impl ActiveRunnable {
395    /// Gets the path of the runnable, if it has one.
396    #[must_use]
397    pub fn path(&self) -> Option<&Path> {
398        match self {
399            Self::Script(script) => script.path(),
400            Self::Module(module) => module.path(),
401        }
402    }
403}
404
405impl Vm {
406    /// Creates a new virtual machine.
407    pub(crate) fn new(realm: Realm) -> Self {
408        let mut frames = Vec::with_capacity(16);
409        frames.push(CallFrame::new(
410            Gc::new(CodeBlock::new(JsString::default(), 0, true)),
411            None,
412            EnvironmentStack::new(),
413            realm,
414        ));
415        Self {
416            frames,
417            stack: Stack::new(1024),
418            return_value: JsValue::undefined(),
419            pending_exception: None,
420            runtime_limits: RuntimeLimits::default(),
421            native_active_function: None,
422            host_call_depth: 0,
423            shadow_stack: ShadowStack::default(),
424            #[cfg(feature = "trace")]
425            trace: false,
426            #[cfg(feature = "trace")]
427            current_frame: None,
428        }
429    }
430
431    #[track_caller]
432    #[inline]
433    pub(crate) fn set_register(&mut self, index: usize, value: JsValue) {
434        let rp = self.frame().rp as usize;
435        debug_assert!(
436            rp + index < self.stack.stack.len(),
437            "register index out of bounds: rp {rp}, index {index}, stack len {}",
438            self.stack.stack.len()
439        );
440        // SAFETY: Register indices are determined by the bytecode compiler and are
441        // guaranteed to be within the register bounds for well-formed bytecode. The
442        // debug_assert above catches any compiler bugs during development.
443        unsafe {
444            *self.stack.stack.get_unchecked_mut(rp + index) = value;
445        }
446    }
447
448    #[track_caller]
449    #[inline]
450    pub(crate) fn get_register(&self, index: usize) -> &JsValue {
451        let rp = self.frame().rp as usize;
452        debug_assert!(
453            rp + index < self.stack.stack.len(),
454            "register index out of bounds: rp {rp}, index {index}, stack len {}",
455            self.stack.stack.len()
456        );
457        // SAFETY: Register indices are determined by the bytecode compiler and are
458        // guaranteed to be within the register bounds for well-formed bytecode. The
459        // debug_assert above catches any compiler bugs during development.
460        unsafe { self.stack.stack.get_unchecked(rp + index) }
461    }
462
463    /// Takes the value from a register, replacing it with `undefined`.
464    ///
465    /// Use this instead of `get_register().clone()` when the register value is
466    /// consumed and won't be read again, to avoid unnecessary Gc refcount increments.
467    #[track_caller]
468    #[inline]
469    pub(crate) fn take_register(&mut self, index: usize) -> JsValue {
470        let rp = self.frame().rp as usize;
471        debug_assert!(
472            rp + index < self.stack.stack.len(),
473            "register index out of bounds: rp {rp}, index {index}, stack len {}",
474            self.stack.stack.len()
475        );
476        // SAFETY: Register indices are determined by the bytecode compiler and are
477        // guaranteed to be within the register bounds for well-formed bytecode. The
478        // debug_assert above catches any compiler bugs during development.
479        unsafe { std::mem::take(self.stack.stack.get_unchecked_mut(rp + index)) }
480    }
481
482    /// Set the promise capability for the current frame.
483    #[track_caller]
484    pub(crate) fn set_promise_capability(
485        &mut self,
486        promise_capability: PromiseCapability,
487    ) -> JsResult<()> {
488        #[cfg(debug_assertions)]
489        {
490            if !self.frame().code_block().is_async() {
491                return Err(crate::error::PanicError::new(
492                    "only async functions and modules with a top-level-await \
493                    can have a promise capability",
494                )
495                .into());
496            }
497        }
498
499        let rp = self.frame().rp as usize;
500        self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX] =
501            promise_capability.promise.into();
502        self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX] =
503            promise_capability.functions.resolve.into();
504        self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX] =
505            promise_capability.functions.reject.into();
506
507        Ok(())
508    }
509
510    /// Get the promise capability for the current frame.
511    #[track_caller]
512    pub(crate) fn get_promise_capability(&self) -> JsResult<PromiseCapability> {
513        #[cfg(debug_assertions)]
514        if !self.frame().code_block().is_async() {
515            return Err(crate::error::PanicError::new(
516                "cannot get promise capability from non-async code",
517            )
518            .into());
519        }
520
521        let rp = self.frame().rp as usize;
522        let promise = self
523            .stack
524            .stack
525            .get(rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX)
526            .and_then(JsValue::as_object)
527            .js_expect("registers must have a promise capability")?;
528        let resolve = self
529            .stack
530            .stack
531            .get(rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX)
532            .and_then(JsValue::as_object)
533            .and_then(JsFunction::from_object)
534            .js_expect("registers must have a resolve function")?;
535        let reject = self
536            .stack
537            .stack
538            .get(rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX)
539            .and_then(JsValue::as_object)
540            .and_then(JsFunction::from_object)
541            .js_expect("registers must have a reject function")?;
542
543        Ok(PromiseCapability {
544            promise,
545            functions: ResolvingFunctions { resolve, reject },
546        })
547    }
548
549    /// Get the async generator object for the current frame.
550    #[track_caller]
551    pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
552        if !self.frame().code_block().is_async_generator() {
553            return None;
554        }
555
556        let rp = self.frame().rp as usize;
557        self.stack
558            .stack
559            .get(rp + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX)
560            .expect("registers must have an async generator object")
561            .as_object()
562    }
563
564    /// Retrieves the VM frame.
565    ///
566    /// NOTE: When you need a `&CallFrame` alongside a mutable borrow of another
567    /// `Vm` field (e.g. `stack`), use `self.vm.frames.last().expect("frame must exist")` instead
568    /// so that the borrow checker can split the borrows.
569    #[track_caller]
570    #[inline]
571    pub(crate) fn frame(&self) -> &CallFrame {
572        // SAFETY: `frames` always contains at least the dummy frame.
573        unsafe { self.frames.last().unwrap_unchecked() }
574    }
575
576    /// Retrieves the VM frame mutably.
577    ///
578    /// NOTE: When you need a `&mut CallFrame` alongside a mutable borrow of another
579    /// `Vm` field (e.g. `stack`), use `self.vm.frames.last_mut().expect("frame must exist")` instead
580    /// so that the borrow checker can split the borrows.
581    #[track_caller]
582    #[inline]
583    pub(crate) fn frame_mut(&mut self) -> &mut CallFrame {
584        // SAFETY: `frames` always contains at least the dummy frame.
585        unsafe { self.frames.last_mut().unwrap_unchecked() }
586    }
587
588    pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
589        // Each function call starts with an implicit `undefined` return value.
590        self.return_value = JsValue::undefined();
591
592        // NOTE: We need to check if we already pushed the registers,
593        //       since generator-like functions push the same call
594        //       frame with pre-built stack and registers (fp and rp already set).
595        if !frame.registers_already_pushed() {
596            let current_stack_length = self.stack.stack.len() as u32;
597            frame.fp = current_stack_length - frame.argument_count - CallFrame::FUNCTION_PROLOGUE;
598
599            let register_count = frame.code_block.register_count as usize;
600            frame.rp = self.stack.stack.len() as u32;
601            self.stack.stack.resize(
602                self.stack.stack.len() + register_count,
603                JsValue::undefined(),
604            );
605        }
606
607        // Keep carrying the last active runnable in case the current callframe
608        // yields.
609        if frame.active_runnable.is_none() {
610            let current = self.frame();
611            frame.active_runnable.clone_from(&current.active_runnable);
612        }
613
614        let current_pc = self.frame().pc;
615        self.shadow_stack
616            .push_bytecode(current_pc, frame.code_block().source_info.clone());
617
618        self.frames.push(frame);
619    }
620
621    pub(crate) fn push_frame_with_stack(
622        &mut self,
623        frame: CallFrame,
624        this: JsValue,
625        function: JsValue,
626    ) {
627        self.stack.push(this);
628        self.stack.push(function);
629
630        self.push_frame(frame);
631    }
632
633    pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
634        // Don't pop the dummy frame (index 0).
635        if self.frames.len() <= 1 {
636            return None;
637        }
638        self.shadow_stack.pop();
639        self.frames.pop()
640    }
641
642    /// Handles an exception thrown at position `pc`.
643    ///
644    /// Returns `true` if the exception was handled, `false` otherwise.
645    #[inline]
646    pub(crate) fn handle_exception_at(&mut self, pc: u32) -> bool {
647        let frame = self.frame_mut();
648        let Some((_, handler)) = frame.code_block().find_handler(pc) else {
649            return false;
650        };
651
652        let catch_address = handler.handler();
653        let environment_sp = frame.env_fp + handler.environment_count;
654
655        // Go to handler location.
656        frame.pc = u32::from(catch_address);
657
658        self.frame_mut()
659            .environments
660            .truncate(environment_sp as usize);
661
662        true
663    }
664
665    pub(crate) fn get_return_value(&self) -> JsValue {
666        self.return_value.clone()
667    }
668
669    pub(crate) fn set_return_value(&mut self, value: JsValue) {
670        self.return_value = value;
671    }
672
673    pub(crate) fn take_return_value(&mut self) -> JsValue {
674        std::mem::take(&mut self.return_value)
675    }
676}
677
678#[allow(clippy::print_stdout)]
679#[cfg(feature = "trace")]
680impl Context {
681    const COLUMN_WIDTH: usize = 26;
682    const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
683    const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
684    const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
685    const NUMBER_OF_COLUMNS: usize = 4;
686
687    pub(crate) fn trace_call_frame(&self) {
688        let frame = self.vm.frame();
689        let msg = if self.vm.frames.is_empty() {
690            " VM Start ".to_string()
691        } else {
692            format!(
693                " Call Frame '{}'{} ",
694                frame.code_block().name().to_std_string_escaped(),
695                if frame.code_block().name().is_empty() {
696                    format!(" [anon#{}]", frame.code_block().debug_id)
697                } else {
698                    String::new()
699                }
700            )
701        };
702
703        // Only print a functions compiled output if it has not been printed already
704        if !frame.code_block.traced.get() {
705            println!("{}", frame.code_block);
706            frame.code_block.traced.set(true);
707        }
708        println!(
709            "{msg:-^width$}",
710            width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
711        );
712        println!(
713            "{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
714            "Time",
715            "Opcode",
716            "Operands",
717            TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
718            OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
719            OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
720        );
721    }
722
723    fn trace_execute_instruction<F>(
724        &mut self,
725        f: F,
726        opcode: Opcode,
727    ) -> ControlFlow<CompletionRecord>
728    where
729        F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
730    {
731        if self.vm.current_frame != Some(self.vm.frame()) {
732            println!();
733            self.trace_call_frame();
734            self.vm.current_frame = Some(self.vm.frame());
735        }
736        let frame = self.vm.frame();
737        let (instruction, _) = frame
738            .code_block
739            .bytecode
740            .next_instruction(frame.pc as usize);
741        let operands = self
742            .vm
743            .frame()
744            .code_block()
745            .instruction_operands(&instruction);
746
747        let instant = Instant::now();
748        let result = self.execute_instruction(f, opcode);
749        let duration = instant.elapsed();
750
751        let stack = self
752            .vm
753            .stack
754            .display_trace(self.vm.frame(), self.vm.frames.len() - 1);
755
756        println!(
757            "{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
758            format!("{}μs", duration.as_micros()),
759            format!("{}", opcode.as_str()),
760            TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
761            OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
762            OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
763        );
764
765        result
766    }
767}
768
769impl Context {
770    fn execute_instruction<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
771    where
772        F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
773    {
774        f(self, opcode)
775    }
776
777    fn execute_one<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
778    where
779        F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
780    {
781        #[cfg(feature = "fuzz")]
782        {
783            use crate::error::EngineError;
784            if self.instructions_remaining == 0 {
785                return ControlFlow::Break(CompletionRecord::Throw(
786                    EngineError::NoInstructionsRemain.into(),
787                ));
788            }
789            self.instructions_remaining -= 1;
790        }
791
792        #[cfg(feature = "trace")]
793        if self.vm.trace || self.vm.frame().code_block.traceable() {
794            self.trace_execute_instruction(f, opcode)
795        } else {
796            self.execute_instruction(f, opcode)
797        }
798
799        #[cfg(not(feature = "trace"))]
800        self.execute_instruction(f, opcode)
801    }
802
803    fn handle_error(&mut self, mut err: JsError) -> ControlFlow<CompletionRecord> {
804        // Capture the backtrace early, before any exception handler check,
805        // so that errors caught by internal handlers (e.g. async module
806        // evaluation) still carry source position information.
807        if err.backtrace.is_none() {
808            err.backtrace = Some(
809                self.vm
810                    .shadow_stack
811                    .take(self.vm.runtime_limits.backtrace_limit(), self.vm.frame().pc),
812            );
813        }
814
815        // If we hit the execution step limit, bubble up the error to the
816        // (Rust) caller instead of trying to handle as an exception.
817        if !err.is_catchable() {
818            let mut frame = None;
819            let mut env_fp = self.vm.frame().environments.len();
820            loop {
821                if self.vm.frame().exit_early() {
822                    break;
823                }
824
825                env_fp = self.vm.frame().env_fp as usize;
826
827                let Some(f) = self.vm.pop_frame() else {
828                    break;
829                };
830                frame = Some(f);
831            }
832            self.vm.frame_mut().environments.truncate(env_fp);
833            if let Some(frame) = frame {
834                self.vm.stack.truncate_to_frame(&frame);
835            }
836            return ControlFlow::Break(CompletionRecord::Throw(err));
837        }
838
839        if let Some(native) = err.as_native_mut()
840            && let ErrorStack::Position(position) = &mut native.stack.0
841        {
842            let backtrace = self.vm.shadow_stack.take_and_push(
843                self.vm.runtime_limits.backtrace_limit(),
844                self.vm.frame().pc,
845                position.clone(),
846            );
847            native.stack.0 = ErrorStack::Backtrace(backtrace);
848        }
849
850        // Note: -1 because we increment after fetching the opcode.
851        let pc = self.vm.frame().pc.saturating_sub(1);
852        if self.vm.handle_exception_at(pc) {
853            self.vm.pending_exception = Some(err);
854            return ControlFlow::Continue(());
855        }
856
857        // Inject realm before crossing the function boundary
858        let err = err.inject_realm(self.realm().clone());
859
860        self.vm.pending_exception = Some(err);
861        self.handle_throw()
862    }
863
864    fn handle_return(&mut self) -> ControlFlow<CompletionRecord> {
865        let exit_early = self.vm.frame().exit_early();
866        let frame = self.vm.frames.last().expect("frame must exist");
867        self.vm.stack.truncate_to_frame(frame);
868
869        let result = self.vm.take_return_value();
870        if exit_early {
871            return ControlFlow::Break(CompletionRecord::Return(result));
872        }
873
874        self.vm.stack.push(result);
875        self.vm.pop_frame().expect("frame must exist");
876        ControlFlow::Continue(())
877    }
878
879    fn handle_yield(&mut self) -> ControlFlow<CompletionRecord> {
880        let result = self.vm.take_return_value();
881        if self.vm.frame().exit_early() {
882            return ControlFlow::Break(CompletionRecord::Normal(result));
883        }
884
885        self.vm.stack.push(result);
886        self.vm.pop_frame().expect("frame must exist");
887        ControlFlow::Continue(())
888    }
889
890    fn handle_throw(&mut self) -> ControlFlow<CompletionRecord> {
891        if self
892            .vm
893            .pending_exception
894            .as_ref()
895            .is_some_and(|err| err.backtrace.is_none())
896        {
897            let pc = self.vm.frames.last().expect("frame must exist").pc;
898            let limit = self.vm.runtime_limits.backtrace_limit();
899            let backtrace = self.vm.shadow_stack.take(limit, pc);
900            self.vm
901                .pending_exception
902                .as_mut()
903                .expect("pending exception must exist")
904                .backtrace = Some(backtrace);
905        }
906
907        let mut env_fp = self.vm.frame().env_fp;
908        if self.vm.frame().exit_early() {
909            self.vm.frame_mut().environments.truncate(env_fp as usize);
910            let frame = self.vm.frames.last().expect("frame must exist");
911            self.vm.stack.truncate_to_frame(frame);
912            return ControlFlow::Break(CompletionRecord::Throw(
913                self.vm
914                    .pending_exception
915                    .take()
916                    .expect("Err must exist for a CompletionType::Throw"),
917            ));
918        }
919
920        let mut frame = self.vm.pop_frame().expect("frame must exist");
921
922        loop {
923            env_fp = self.vm.frame().env_fp;
924            let pc = self.vm.frame().pc;
925            let exit_early = self.vm.frame().exit_early();
926
927            if self.vm.handle_exception_at(pc) {
928                return ControlFlow::Continue(());
929            }
930
931            if exit_early {
932                return ControlFlow::Break(CompletionRecord::Throw(
933                    self.vm
934                        .pending_exception
935                        .take()
936                        .expect("Err must exist for a CompletionType::Throw"),
937                ));
938            }
939
940            let Some(f) = self.vm.pop_frame() else {
941                break;
942            };
943            frame = f;
944        }
945        self.vm.frame_mut().environments.truncate(env_fp as usize);
946        self.vm.stack.truncate_to_frame(&frame);
947        ControlFlow::Continue(())
948    }
949
950    /// Runs the current frame to completion, yielding to the caller each time `budget`
951    /// "clock cycles" have passed.
952    #[allow(clippy::future_not_send)]
953    pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord {
954        let mut runtime_budget: u32 = budget;
955
956        while let Some(byte) = self
957            .vm
958            .frame()
959            .code_block
960            .bytecode
961            .bytes
962            .get(self.vm.frame().pc as usize)
963        {
964            let opcode = Opcode::decode(*byte);
965
966            match self.execute_one(
967                |context, opcode| {
968                    let frame = context.vm.frame();
969                    let pc = frame.pc as usize;
970
971                    OPCODE_HANDLERS_BUDGET[opcode as usize](context, pc, &mut runtime_budget)
972                },
973                opcode,
974            ) {
975                ControlFlow::Continue(()) => {}
976                ControlFlow::Break(value) => return value,
977            }
978
979            if runtime_budget == 0 {
980                runtime_budget = budget;
981                yield_now().await;
982            }
983        }
984
985        CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
986    }
987
988    pub(crate) fn run(&mut self) -> CompletionRecord {
989        while let Some(byte) = self
990            .vm
991            .frame()
992            .code_block
993            .bytecode
994            .bytes
995            .get(self.vm.frame().pc as usize)
996        {
997            let opcode = Opcode::decode(*byte);
998
999            match self.execute_one(
1000                |context, opcode| {
1001                    let frame = context.vm.frame();
1002                    let pc = frame.pc as usize;
1003
1004                    OPCODE_HANDLERS[opcode as usize](context, pc)
1005                },
1006                opcode,
1007            ) {
1008                ControlFlow::Continue(()) => {}
1009                ControlFlow::Break(value) => return value,
1010            }
1011        }
1012
1013        CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
1014    }
1015
1016    /// Checks if we haven't exceeded the defined runtime limits.
1017    pub(crate) fn check_runtime_limits(&self) -> JsResult<()> {
1018        // Must throw if the number of recursive calls exceeds the defined limit.
1019        //
1020        // `host_call_depth` accounts for nested host calls that re-enter the VM by invoking
1021        // `Context::run()` recursively (for example, accessor calls).
1022        // Subtract 1 to exclude the dummy frame at index 0.
1023        let recursion_depth = (self.vm.frames.len() - 1).saturating_add(self.vm.host_call_depth);
1024        if self.vm.runtime_limits.recursion_limit() <= recursion_depth {
1025            return Err(RuntimeLimitError::Recursion.into());
1026        }
1027        // Must throw if the stack size exceeds the defined maximum length.
1028        if self.vm.runtime_limits.stack_size_limit() <= self.vm.stack.stack.len() {
1029            return Err(RuntimeLimitError::StackSize.into());
1030        }
1031
1032        Ok(())
1033    }
1034}
1035
1036/// Yields once to the executor.
1037fn yield_now() -> impl Future<Output = ()> {
1038    struct YieldNow(bool);
1039
1040    impl Future for YieldNow {
1041        type Output = ();
1042
1043        fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
1044            if self.0 {
1045                task::Poll::Ready(())
1046            } else {
1047                self.0 = true;
1048                cx.waker().wake_by_ref();
1049                task::Poll::Pending
1050            }
1051        }
1052    }
1053
1054    YieldNow(false)
1055}