boa_engine/vm/call_frame/
mod.rs

1//! `CallFrame`
2//!
3//! This module will provides everything needed to implement the `CallFrame`
4
5use crate::{
6    builtins::{
7        iterable::IteratorRecord,
8        promise::{PromiseCapability, ResolvingFunctions},
9    },
10    environments::EnvironmentStack,
11    object::{JsFunction, JsObject},
12    realm::Realm,
13    vm::CodeBlock,
14    JsValue,
15};
16use boa_ast::scope::BindingLocator;
17use boa_gc::{Finalize, Gc, Trace};
18use thin_vec::ThinVec;
19
20use super::{ActiveRunnable, Vm};
21
22bitflags::bitflags! {
23    /// Flags associated with a [`CallFrame`].
24    #[derive(Debug, Default, Clone, Copy)]
25    pub(crate) struct CallFrameFlags: u8 {
26        /// When we return from this [`CallFrame`] to stop execution and
27        /// return from [`crate::Context::run()`], and leave the remaining [`CallFrame`]s unchanged.
28        const EXIT_EARLY = 0b0000_0001;
29
30        /// Was this [`CallFrame`] created from the `__construct__()` internal object method?
31        const CONSTRUCT = 0b0000_0010;
32
33        /// Does this [`CallFrame`] need to push registers on [`Vm::push_frame()`].
34        const REGISTERS_ALREADY_PUSHED = 0b0000_0100;
35
36        /// If the `this` value has been cached.
37        const THIS_VALUE_CACHED = 0b0000_1000;
38    }
39}
40
41/// A `CallFrame` holds the state of a function call.
42#[derive(Clone, Debug, Finalize, Trace)]
43pub struct CallFrame {
44    pub(crate) code_block: Gc<CodeBlock>,
45    pub(crate) pc: u32,
46    /// The register pointer, points to the first register in the stack.
47    ///
48    // TODO: Check if storing the frame pointer instead of argument count and computing the
49    //       argument count based on the pointers would be better for accessing the arguments
50    //       and the elements before the register pointer.
51    pub(crate) rp: u32,
52    pub(crate) argument_count: u32,
53    pub(crate) env_fp: u32,
54
55    // Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
56    pub(crate) iterators: ThinVec<IteratorRecord>,
57
58    // The stack of bindings being updated.
59    // SAFETY: Nothing in `BindingLocator` requires tracing, so this is safe.
60    #[unsafe_ignore_trace]
61    pub(crate) binding_stack: Vec<BindingLocator>,
62
63    // SAFETY: Nothing requires tracing, so this is safe.
64    #[unsafe_ignore_trace]
65    pub(crate) local_binings_initialized: Box<[bool]>,
66
67    /// How many iterations a loop has done.
68    pub(crate) loop_iteration_count: u64,
69
70    /// `[[ScriptOrModule]]`
71    pub(crate) active_runnable: Option<ActiveRunnable>,
72
73    /// \[\[Environment\]\]
74    pub(crate) environments: EnvironmentStack,
75
76    /// \[\[Realm\]\]
77    pub(crate) realm: Realm,
78
79    // SAFETY: Nothing in `CallFrameFlags` requires tracing, so this is safe.
80    #[unsafe_ignore_trace]
81    pub(crate) flags: CallFrameFlags,
82}
83
84/// ---- `CallFrame` public API ----
85impl CallFrame {
86    /// Retrieves the [`CodeBlock`] of this call frame.
87    #[inline]
88    #[must_use]
89    pub const fn code_block(&self) -> &Gc<CodeBlock> {
90        &self.code_block
91    }
92}
93
94/// ---- `CallFrame` creation methods ----
95impl CallFrame {
96    /// This is the size of the function prologue.
97    ///
98    /// The position of the elements are relative to the [`CallFrame::fp`] (register pointer).
99    ///
100    /// ```text
101    ///                      Setup by the caller
102    ///   ┌─────────────────────────────────────────────────────────┐ ┌───── register pointer
103    ///   ▼                                                         ▼ ▼
104    /// | -(2 + N): this | -(1 + N): func | -N: arg1 | ... | -1: argN | 0: local1 | ... | K: localK |
105    ///   ▲                              ▲   ▲                      ▲   ▲                         ▲
106    ///   └──────────────────────────────┘   └──────────────────────┘   └─────────────────────────┘
107    ///         function prologue                    arguments              Setup by the callee
108    ///   ▲
109    ///   └─ Frame pointer
110    /// ```
111    ///
112    /// ### Example
113    ///
114    /// The following function calls, generate the following stack:
115    ///
116    /// ```JavaScript
117    /// function x(a) {
118    /// }
119    /// function y(b, c) {
120    ///     return x(b + c)
121    /// }
122    ///
123    /// y(1, 2)
124    /// ```
125    ///
126    /// ```text
127    ///     caller prologue    caller arguments   callee prologue   callee arguments
128    ///   ┌─────────────────┐   ┌─────────┐   ┌─────────────────┐  ┌──────┐
129    ///   ▼                 ▼   ▼         ▼   │                 ▼  ▼      ▼
130    /// | 0: undefined | 1: y | 2: 1 | 3: 2 | 4: undefined | 5: x | 6:  3 |
131    /// ▲                                   ▲                             ▲
132    /// │       caller register pointer ────┤                             │
133    /// │                                   │                 callee register pointer
134    /// │                             callee frame pointer
135    /// │
136    /// └─────  caller frame pointer
137    /// ```
138    ///
139    /// Some questions:
140    ///
141    /// - Who is responsible for cleaning up the stack after a call? The rust caller.
142    pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
143    pub(crate) const THIS_POSITION: u32 = 2;
144    pub(crate) const FUNCTION_POSITION: u32 = 1;
145    pub(crate) const PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX: u32 = 0;
146    pub(crate) const PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX: u32 = 1;
147    pub(crate) const PROMISE_CAPABILITY_REJECT_REGISTER_INDEX: u32 = 2;
148    pub(crate) const ASYNC_GENERATOR_OBJECT_REGISTER_INDEX: u32 = 3;
149
150    /// Creates a new `CallFrame` with the provided `CodeBlock`.
151    pub(crate) fn new(
152        code_block: Gc<CodeBlock>,
153        active_runnable: Option<ActiveRunnable>,
154        environments: EnvironmentStack,
155        realm: Realm,
156    ) -> Self {
157        let local_binings_initialized = code_block.local_bindings_initialized.clone();
158        Self {
159            code_block,
160            pc: 0,
161            rp: 0,
162            env_fp: 0,
163            argument_count: 0,
164            iterators: ThinVec::new(),
165            binding_stack: Vec::new(),
166            local_binings_initialized,
167            loop_iteration_count: 0,
168            active_runnable,
169            environments,
170            realm,
171            flags: CallFrameFlags::empty(),
172        }
173    }
174
175    /// Updates a `CallFrame`'s `argument_count` field with the value provided.
176    pub(crate) fn with_argument_count(mut self, count: u32) -> Self {
177        self.argument_count = count;
178        self
179    }
180
181    /// Updates a `CallFrame`'s `env_fp` field with the value provided.
182    pub(crate) fn with_env_fp(mut self, env_fp: u32) -> Self {
183        self.env_fp = env_fp;
184        self
185    }
186
187    /// Updates a `CallFrame`'s `flags` field with the value provided.
188    pub(crate) fn with_flags(mut self, flags: CallFrameFlags) -> Self {
189        self.flags = flags;
190        self
191    }
192
193    pub(crate) fn this(&self, vm: &Vm) -> JsValue {
194        let this_index = self.rp - self.argument_count - Self::THIS_POSITION;
195        vm.stack[this_index as usize].clone()
196    }
197
198    pub(crate) fn function(&self, vm: &Vm) -> Option<JsObject> {
199        let function_index = self.rp - self.argument_count - Self::FUNCTION_POSITION;
200        if let Some(object) = vm.stack[function_index as usize].as_object() {
201            return Some(object.clone());
202        }
203
204        None
205    }
206
207    pub(crate) fn arguments<'stack>(&self, vm: &'stack Vm) -> &'stack [JsValue] {
208        let rp = self.rp as usize;
209        let argument_count = self.argument_count as usize;
210        let arguments_start = rp - argument_count;
211        &vm.stack[arguments_start..rp]
212    }
213
214    pub(crate) fn argument<'stack>(&self, index: usize, vm: &'stack Vm) -> Option<&'stack JsValue> {
215        self.arguments(vm).get(index)
216    }
217
218    pub(crate) fn fp(&self) -> u32 {
219        self.rp - self.argument_count - Self::FUNCTION_PROLOGUE
220    }
221
222    pub(crate) fn restore_stack(&self, vm: &mut Vm) {
223        let fp = self.fp();
224        vm.stack.truncate(fp as usize);
225    }
226
227    /// Returns the async generator object, if the function that this [`CallFrame`] is from an async generator, [`None`] otherwise.
228    pub(crate) fn async_generator_object(&self, stack: &[JsValue]) -> Option<JsObject> {
229        if !self.code_block().is_async_generator() {
230            return None;
231        }
232
233        self.register(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, stack)
234            .as_object()
235            .cloned()
236    }
237
238    pub(crate) fn promise_capability(&self, stack: &[JsValue]) -> Option<PromiseCapability> {
239        if !self.code_block().is_async() {
240            return None;
241        }
242
243        let promise = self
244            .register(Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX, stack)
245            .as_object()
246            .cloned()?;
247        let resolve = self
248            .register(Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX, stack)
249            .as_object()
250            .cloned()
251            .and_then(JsFunction::from_object)?;
252        let reject = self
253            .register(Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX, stack)
254            .as_object()
255            .cloned()
256            .and_then(JsFunction::from_object)?;
257
258        Some(PromiseCapability {
259            promise,
260            functions: ResolvingFunctions { resolve, reject },
261        })
262    }
263
264    pub(crate) fn set_promise_capability(
265        &self,
266        stack: &mut [JsValue],
267        promise_capability: Option<&PromiseCapability>,
268    ) {
269        debug_assert!(
270            self.code_block().is_async(),
271            "Only async functions have a promise capability"
272        );
273
274        self.set_register(
275            Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX,
276            promise_capability
277                .map(PromiseCapability::promise)
278                .cloned()
279                .map_or_else(JsValue::undefined, Into::into),
280            stack,
281        );
282        self.set_register(
283            Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX,
284            promise_capability
285                .map(PromiseCapability::resolve)
286                .cloned()
287                .map_or_else(JsValue::undefined, Into::into),
288            stack,
289        );
290        self.set_register(
291            Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX,
292            promise_capability
293                .map(PromiseCapability::reject)
294                .cloned()
295                .map_or_else(JsValue::undefined, Into::into),
296            stack,
297        );
298    }
299
300    /// Returns the register at the given index.
301    ///
302    /// # Panics
303    ///
304    /// If the index is out of bounds.
305    #[track_caller]
306    pub(crate) fn register<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue {
307        debug_assert!(index < self.code_block().register_count);
308        let at = self.rp + index;
309        &stack[at as usize]
310    }
311
312    /// Sets the register at the given index.
313    ///
314    /// # Panics
315    ///
316    /// If the index is out of bounds.
317    pub(crate) fn set_register(&self, index: u32, value: JsValue, stack: &mut [JsValue]) {
318        debug_assert!(index < self.code_block().register_count);
319        let at = self.rp + index;
320        stack[at as usize] = value;
321    }
322
323    /// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
324    pub(crate) fn exit_early(&self) -> bool {
325        self.flags.contains(CallFrameFlags::EXIT_EARLY)
326    }
327    /// Set the [`CallFrameFlags::EXIT_EARLY`] flag.
328    pub(crate) fn set_exit_early(&mut self, early_exit: bool) {
329        self.flags.set(CallFrameFlags::EXIT_EARLY, early_exit);
330    }
331    /// Does this have the [`CallFrameFlags::CONSTRUCT`] flag.
332    pub(crate) fn construct(&self) -> bool {
333        self.flags.contains(CallFrameFlags::CONSTRUCT)
334    }
335    /// Does this [`CallFrame`] need to push registers on [`Vm::push_frame()`].
336    pub(crate) fn registers_already_pushed(&self) -> bool {
337        self.flags
338            .contains(CallFrameFlags::REGISTERS_ALREADY_PUSHED)
339    }
340    /// Does this [`CallFrame`] have a cached `this` value.
341    ///
342    /// The cached value is placed in the [`CallFrame::THIS_POSITION`] position.
343    pub(crate) fn has_this_value_cached(&self) -> bool {
344        self.flags.contains(CallFrameFlags::THIS_VALUE_CACHED)
345    }
346}
347
348/// ---- `CallFrame` stack methods ----
349impl CallFrame {
350    pub(crate) fn set_register_pointer(&mut self, pointer: u32) {
351        self.rp = pointer;
352    }
353}
354
355/// Indicates how a generator function that has been called/resumed should return.
356#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
357#[repr(u8)]
358#[allow(missing_docs)]
359pub enum GeneratorResumeKind {
360    #[default]
361    Normal = 0,
362    Throw,
363    Return,
364}
365
366impl From<GeneratorResumeKind> for JsValue {
367    fn from(value: GeneratorResumeKind) -> Self {
368        Self::new(value as u8)
369    }
370}
371
372impl JsValue {
373    /// Convert value to [`GeneratorResumeKind`].
374    ///
375    /// # Panics
376    ///
377    /// If not a integer type or not in the range `1..=2`.
378    #[track_caller]
379    pub(crate) fn to_generator_resume_kind(&self) -> GeneratorResumeKind {
380        if let Self::Integer(value) = self {
381            match *value {
382                0 => return GeneratorResumeKind::Normal,
383                1 => return GeneratorResumeKind::Throw,
384                2 => return GeneratorResumeKind::Return,
385                _ => unreachable!("generator kind must be a integer between 1..=2, got {value}"),
386            }
387        }
388
389        unreachable!("generator kind must be a integer type")
390    }
391}