Skip to main content

boa_engine/vm/call_frame/
mod.rs

1//! `CallFrame`
2//!
3//! This module will provides everything needed to implement the `CallFrame`
4
5use super::ActiveRunnable;
6use crate::{
7    JsValue,
8    builtins::iterable::IteratorRecord,
9    bytecompiler::Register,
10    environments::EnvironmentStack,
11    realm::Realm,
12    vm::{CodeBlock, SourcePath},
13};
14use boa_ast::Position;
15use boa_ast::scope::BindingLocator;
16use boa_gc::{Finalize, Gc, Trace};
17use boa_string::JsString;
18use thin_vec::ThinVec;
19
20bitflags::bitflags! {
21    /// Flags associated with a [`CallFrame`].
22    #[derive(Debug, Default, Clone, Copy)]
23    pub(crate) struct CallFrameFlags: u8 {
24        /// When we return from this [`CallFrame`] to stop execution and
25        /// return from [`crate::Context::run()`], and leave the remaining [`CallFrame`]s unchanged.
26        const EXIT_EARLY = 0b0000_0001;
27
28        /// Was this [`CallFrame`] created from the `__construct__()` internal object method?
29        const CONSTRUCT = 0b0000_0010;
30
31        /// Does this [`CallFrame`] need to push registers on [`Vm::push_frame()`].
32        const REGISTERS_ALREADY_PUSHED = 0b0000_0100;
33
34        /// If the `this` value has been cached.
35        const THIS_VALUE_CACHED = 0b0000_1000;
36    }
37}
38
39#[derive(Debug, Clone, Eq, PartialEq)]
40pub struct CallFrameLocation {
41    pub function_name: JsString,
42    pub path: SourcePath,
43    pub position: Option<Position>,
44}
45
46/// A `CallFrame` holds the state of a function call.
47#[derive(Clone, Debug, Finalize, Trace)]
48pub struct CallFrame {
49    pub(crate) code_block: Gc<CodeBlock>,
50    pub(crate) pc: u32,
51    /// The frame pointer, points to the start of this frame's data in the stack
52    /// (i.e., the `this` value position).
53    pub(crate) fp: u32,
54    pub(crate) argument_count: u32,
55    pub(crate) env_fp: u32,
56
57    /// The register pointer, points to the start of this frame's registers on the stack.
58    pub(crate) rp: u32,
59
60    // Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
61    pub(crate) iterators: ThinVec<IteratorRecord>,
62
63    // The stack of bindings being updated.
64    // SAFETY: Nothing in `BindingLocator` requires tracing, so this is safe.
65    #[unsafe_ignore_trace]
66    pub(crate) binding_stack: ThinVec<BindingLocator>,
67
68    /// How many iterations a loop has done.
69    pub(crate) loop_iteration_count: u64,
70
71    /// `[[ScriptOrModule]]`
72    pub(crate) active_runnable: Option<ActiveRunnable>,
73
74    /// \[\[Environment\]\]
75    pub(crate) environments: EnvironmentStack,
76
77    /// \[\[Realm\]\]
78    pub(crate) realm: Realm,
79
80    // SAFETY: Nothing in `CallFrameFlags` requires tracing, so this is safe.
81    #[unsafe_ignore_trace]
82    pub(crate) flags: CallFrameFlags,
83}
84
85/// ---- `CallFrame` public API ----
86impl CallFrame {
87    /// Retrieves the [`CodeBlock`] of this call frame.
88    #[inline]
89    #[must_use]
90    pub const fn code_block(&self) -> &Gc<CodeBlock> {
91        &self.code_block
92    }
93
94    /// Retrieves a tuple of `(`[`JsString`]`, `[`SourcePath`]`, `[`Position`]`)` to know the
95    /// location of the call frame.
96    #[inline]
97    #[must_use]
98    pub fn position(&self) -> CallFrameLocation {
99        let source_info = &self.code_block.source_info;
100        CallFrameLocation {
101            function_name: source_info.function_name().clone(),
102            path: source_info.map().path().clone(),
103            position: source_info.map().find(self.pc),
104        }
105    }
106}
107
108/// ---- `CallFrame` creation methods ----
109impl CallFrame {
110    pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
111    pub(crate) const UNDEFINED_REGISTER_INDEX: usize = 0;
112    pub(crate) const PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX: usize = 1;
113    pub(crate) const PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX: usize = 2;
114    pub(crate) const PROMISE_CAPABILITY_REJECT_REGISTER_INDEX: usize = 3;
115    pub(crate) const ASYNC_GENERATOR_OBJECT_REGISTER_INDEX: usize = 4;
116
117    pub(crate) fn undefined_register() -> Register {
118        Register::persistent(Self::UNDEFINED_REGISTER_INDEX as u32)
119    }
120
121    pub(crate) fn promise_capability_promise_register() -> Register {
122        Register::persistent(Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX as u32)
123    }
124
125    pub(crate) fn promise_capability_resolve_register() -> Register {
126        Register::persistent(Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX as u32)
127    }
128
129    pub(crate) fn promise_capability_reject_register() -> Register {
130        Register::persistent(Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX as u32)
131    }
132
133    pub(crate) fn async_generator_object_register() -> Register {
134        Register::persistent(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX as u32)
135    }
136
137    /// Creates a new `CallFrame` with the provided `CodeBlock`.
138    pub(crate) fn new(
139        code_block: Gc<CodeBlock>,
140        active_runnable: Option<ActiveRunnable>,
141        environments: EnvironmentStack,
142        realm: Realm,
143    ) -> Self {
144        Self {
145            pc: 0,
146            fp: 0,
147            env_fp: 0,
148            argument_count: 0,
149            rp: 0,
150            iterators: ThinVec::new(),
151            binding_stack: ThinVec::new(),
152            code_block,
153            loop_iteration_count: 0,
154            active_runnable,
155            environments,
156            realm,
157            flags: CallFrameFlags::empty(),
158        }
159    }
160
161    /// Updates a `CallFrame`'s `argument_count` field with the value provided.
162    pub(crate) fn with_argument_count(mut self, count: u32) -> Self {
163        self.argument_count = count;
164        self
165    }
166
167    /// Updates a `CallFrame`'s `env_fp` field with the value provided.
168    pub(crate) fn with_env_fp(mut self, env_fp: u32) -> Self {
169        self.env_fp = env_fp;
170        self
171    }
172
173    /// Updates a `CallFrame`'s `flags` field with the value provided.
174    pub(crate) fn with_flags(mut self, flags: CallFrameFlags) -> Self {
175        self.flags = flags;
176        self
177    }
178
179    /// Returns the index of `this` in the stack.
180    pub(crate) fn this_index(&self) -> usize {
181        self.fp as usize
182    }
183
184    /// Returns the index of the function in the stack.
185    pub(crate) fn function_index(&self) -> usize {
186        self.fp as usize + 1
187    }
188
189    /// Returns the range of the arguments in the stack.
190    pub(crate) fn arguments_range(&self) -> std::ops::Range<usize> {
191        let start = self.fp as usize + Self::FUNCTION_PROLOGUE as usize;
192        start..start + self.argument_count as usize
193    }
194
195    /// Returns the frame pointer of this `CallFrame`.
196    pub(crate) fn frame_pointer(&self) -> usize {
197        self.fp as usize
198    }
199
200    /// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
201    pub(crate) fn exit_early(&self) -> bool {
202        self.flags.contains(CallFrameFlags::EXIT_EARLY)
203    }
204
205    /// Set the [`CallFrameFlags::EXIT_EARLY`] flag.
206    pub(crate) fn set_exit_early(&mut self, early_exit: bool) {
207        self.flags.set(CallFrameFlags::EXIT_EARLY, early_exit);
208    }
209
210    /// Does this have the [`CallFrameFlags::CONSTRUCT`] flag.
211    pub(crate) fn construct(&self) -> bool {
212        self.flags.contains(CallFrameFlags::CONSTRUCT)
213    }
214
215    /// Does this [`CallFrame`] need to push registers on [`super::Vm::push_frame()`].
216    pub(crate) fn registers_already_pushed(&self) -> bool {
217        self.flags
218            .contains(CallFrameFlags::REGISTERS_ALREADY_PUSHED)
219    }
220
221    /// Does this [`CallFrame`] have a cached `this` value.
222    ///
223    /// The cached value is placed in the `this` position.
224    pub(crate) fn has_this_value_cached(&self) -> bool {
225        self.flags.contains(CallFrameFlags::THIS_VALUE_CACHED)
226    }
227}
228
229/// Indicates how a generator function that has been called/resumed should return.
230#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
231#[repr(u8)]
232#[allow(missing_docs)]
233pub enum GeneratorResumeKind {
234    #[default]
235    Normal = 0,
236    Throw,
237    Return,
238}
239
240impl From<GeneratorResumeKind> for JsValue {
241    fn from(value: GeneratorResumeKind) -> Self {
242        Self::new(value as u8)
243    }
244}
245
246impl JsValue {
247    /// Convert value to [`GeneratorResumeKind`].
248    ///
249    /// # Panics
250    ///
251    /// If not a integer type or not in the range `0..=2`.
252    #[track_caller]
253    pub(crate) fn to_generator_resume_kind(&self) -> GeneratorResumeKind {
254        if let Some(value) = self.as_i32() {
255            match value {
256                0 => return GeneratorResumeKind::Normal,
257                1 => return GeneratorResumeKind::Throw,
258                2 => return GeneratorResumeKind::Return,
259                _ => unreachable!("generator kind must be an integer between 1..=2, got {value}"),
260            }
261        }
262
263        unreachable!("generator kind must be an integer type")
264    }
265}