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