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