boa_engine/vm/call_frame/
mod.rs1use 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 #[derive(Debug, Default, Clone, Copy)]
25 pub(crate) struct CallFrameFlags: u8 {
26 const EXIT_EARLY = 0b0000_0001;
29
30 const CONSTRUCT = 0b0000_0010;
32
33 const REGISTERS_ALREADY_PUSHED = 0b0000_0100;
35
36 const THIS_VALUE_CACHED = 0b0000_1000;
38 }
39}
40
41#[derive(Clone, Debug, Finalize, Trace)]
43pub struct CallFrame {
44 pub(crate) code_block: Gc<CodeBlock>,
45 pub(crate) pc: u32,
46 pub(crate) rp: u32,
52 pub(crate) argument_count: u32,
53 pub(crate) env_fp: u32,
54
55 pub(crate) iterators: ThinVec<IteratorRecord>,
57
58 #[unsafe_ignore_trace]
61 pub(crate) binding_stack: Vec<BindingLocator>,
62
63 #[unsafe_ignore_trace]
65 pub(crate) local_binings_initialized: Box<[bool]>,
66
67 pub(crate) loop_iteration_count: u64,
69
70 pub(crate) active_runnable: Option<ActiveRunnable>,
72
73 pub(crate) environments: EnvironmentStack,
75
76 pub(crate) realm: Realm,
78
79 #[unsafe_ignore_trace]
81 pub(crate) flags: CallFrameFlags,
82}
83
84impl CallFrame {
86 #[inline]
88 #[must_use]
89 pub const fn code_block(&self) -> &Gc<CodeBlock> {
90 &self.code_block
91 }
92}
93
94impl CallFrame {
96 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 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 pub(crate) fn with_argument_count(mut self, count: u32) -> Self {
177 self.argument_count = count;
178 self
179 }
180
181 pub(crate) fn with_env_fp(mut self, env_fp: u32) -> Self {
183 self.env_fp = env_fp;
184 self
185 }
186
187 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 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 #[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 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 pub(crate) fn exit_early(&self) -> bool {
325 self.flags.contains(CallFrameFlags::EXIT_EARLY)
326 }
327 pub(crate) fn set_exit_early(&mut self, early_exit: bool) {
329 self.flags.set(CallFrameFlags::EXIT_EARLY, early_exit);
330 }
331 pub(crate) fn construct(&self) -> bool {
333 self.flags.contains(CallFrameFlags::CONSTRUCT)
334 }
335 pub(crate) fn registers_already_pushed(&self) -> bool {
337 self.flags
338 .contains(CallFrameFlags::REGISTERS_ALREADY_PUSHED)
339 }
340 pub(crate) fn has_this_value_cached(&self) -> bool {
344 self.flags.contains(CallFrameFlags::THIS_VALUE_CACHED)
345 }
346}
347
348impl CallFrame {
350 pub(crate) fn set_register_pointer(&mut self, pointer: u32) {
351 self.rp = pointer;
352 }
353}
354
355#[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 #[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}