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