use crate::{
builtins::{
iterable::IteratorRecord,
promise::{PromiseCapability, ResolvingFunctions},
},
environments::EnvironmentStack,
object::{JsFunction, JsObject},
realm::Realm,
vm::CodeBlock,
JsValue,
};
use boa_ast::scope::BindingLocator;
use boa_gc::{Finalize, Gc, Trace};
use thin_vec::ThinVec;
use super::{ActiveRunnable, Vm};
bitflags::bitflags! {
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct CallFrameFlags: u8 {
const EXIT_EARLY = 0b0000_0001;
const CONSTRUCT = 0b0000_0010;
const REGISTERS_ALREADY_PUSHED = 0b0000_0100;
const THIS_VALUE_CACHED = 0b0000_1000;
}
}
#[derive(Clone, Debug, Finalize, Trace)]
pub struct CallFrame {
pub(crate) code_block: Gc<CodeBlock>,
pub(crate) pc: u32,
pub(crate) rp: u32,
pub(crate) argument_count: u32,
pub(crate) env_fp: u32,
pub(crate) iterators: ThinVec<IteratorRecord>,
#[unsafe_ignore_trace]
pub(crate) binding_stack: Vec<BindingLocator>,
#[unsafe_ignore_trace]
pub(crate) local_binings_initialized: Box<[bool]>,
pub(crate) loop_iteration_count: u64,
pub(crate) active_runnable: Option<ActiveRunnable>,
pub(crate) environments: EnvironmentStack,
pub(crate) realm: Realm,
#[unsafe_ignore_trace]
pub(crate) flags: CallFrameFlags,
}
impl CallFrame {
#[inline]
#[must_use]
pub const fn code_block(&self) -> &Gc<CodeBlock> {
&self.code_block
}
}
impl CallFrame {
pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
pub(crate) const THIS_POSITION: u32 = 2;
pub(crate) const FUNCTION_POSITION: u32 = 1;
pub(crate) const PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX: u32 = 0;
pub(crate) const PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX: u32 = 1;
pub(crate) const PROMISE_CAPABILITY_REJECT_REGISTER_INDEX: u32 = 2;
pub(crate) const ASYNC_GENERATOR_OBJECT_REGISTER_INDEX: u32 = 3;
pub(crate) fn new(
code_block: Gc<CodeBlock>,
active_runnable: Option<ActiveRunnable>,
environments: EnvironmentStack,
realm: Realm,
) -> Self {
let local_binings_initialized = code_block.local_bindings_initialized.clone();
Self {
code_block,
pc: 0,
rp: 0,
env_fp: 0,
argument_count: 0,
iterators: ThinVec::new(),
binding_stack: Vec::new(),
local_binings_initialized,
loop_iteration_count: 0,
active_runnable,
environments,
realm,
flags: CallFrameFlags::empty(),
}
}
pub(crate) fn with_argument_count(mut self, count: u32) -> Self {
self.argument_count = count;
self
}
pub(crate) fn with_env_fp(mut self, env_fp: u32) -> Self {
self.env_fp = env_fp;
self
}
pub(crate) fn with_flags(mut self, flags: CallFrameFlags) -> Self {
self.flags = flags;
self
}
pub(crate) fn this(&self, vm: &Vm) -> JsValue {
let this_index = self.rp - self.argument_count - Self::THIS_POSITION;
vm.stack[this_index as usize].clone()
}
pub(crate) fn function(&self, vm: &Vm) -> Option<JsObject> {
let function_index = self.rp - self.argument_count - Self::FUNCTION_POSITION;
if let Some(object) = vm.stack[function_index as usize].as_object() {
return Some(object.clone());
}
None
}
pub(crate) fn arguments<'stack>(&self, vm: &'stack Vm) -> &'stack [JsValue] {
let rp = self.rp as usize;
let argument_count = self.argument_count as usize;
let arguments_start = rp - argument_count;
&vm.stack[arguments_start..rp]
}
pub(crate) fn argument<'stack>(&self, index: usize, vm: &'stack Vm) -> Option<&'stack JsValue> {
self.arguments(vm).get(index)
}
pub(crate) fn fp(&self) -> u32 {
self.rp - self.argument_count - Self::FUNCTION_PROLOGUE
}
pub(crate) fn restore_stack(&self, vm: &mut Vm) {
let fp = self.fp();
vm.stack.truncate(fp as usize);
}
pub(crate) fn async_generator_object(&self, stack: &[JsValue]) -> Option<JsObject> {
if !self.code_block().is_async_generator() {
return None;
}
self.register(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, stack)
.as_object()
.cloned()
}
pub(crate) fn promise_capability(&self, stack: &[JsValue]) -> Option<PromiseCapability> {
if !self.code_block().is_async() {
return None;
}
let promise = self
.register(Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX, stack)
.as_object()
.cloned()?;
let resolve = self
.register(Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX, stack)
.as_object()
.cloned()
.and_then(JsFunction::from_object)?;
let reject = self
.register(Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX, stack)
.as_object()
.cloned()
.and_then(JsFunction::from_object)?;
Some(PromiseCapability {
promise,
functions: ResolvingFunctions { resolve, reject },
})
}
pub(crate) fn set_promise_capability(
&self,
stack: &mut [JsValue],
promise_capability: Option<&PromiseCapability>,
) {
debug_assert!(
self.code_block().is_async(),
"Only async functions have a promise capability"
);
self.set_register(
Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::promise)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_register(
Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::resolve)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_register(
Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::reject)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
}
#[track_caller]
pub(crate) fn register<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue {
debug_assert!(index < self.code_block().register_count);
let at = self.rp + index;
&stack[at as usize]
}
pub(crate) fn set_register(&self, index: u32, value: JsValue, stack: &mut [JsValue]) {
debug_assert!(index < self.code_block().register_count);
let at = self.rp + index;
stack[at as usize] = value;
}
pub(crate) fn exit_early(&self) -> bool {
self.flags.contains(CallFrameFlags::EXIT_EARLY)
}
pub(crate) fn set_exit_early(&mut self, early_exit: bool) {
self.flags.set(CallFrameFlags::EXIT_EARLY, early_exit);
}
pub(crate) fn construct(&self) -> bool {
self.flags.contains(CallFrameFlags::CONSTRUCT)
}
pub(crate) fn registers_already_pushed(&self) -> bool {
self.flags
.contains(CallFrameFlags::REGISTERS_ALREADY_PUSHED)
}
pub(crate) fn has_this_value_cached(&self) -> bool {
self.flags.contains(CallFrameFlags::THIS_VALUE_CACHED)
}
}
impl CallFrame {
pub(crate) fn set_register_pointer(&mut self, pointer: u32) {
self.rp = pointer;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum GeneratorResumeKind {
#[default]
Normal = 0,
Throw,
Return,
}
impl From<GeneratorResumeKind> for JsValue {
fn from(value: GeneratorResumeKind) -> Self {
Self::new(value as u8)
}
}
impl JsValue {
#[track_caller]
pub(crate) fn to_generator_resume_kind(&self) -> GeneratorResumeKind {
if let Self::Integer(value) = self {
match *value {
0 => return GeneratorResumeKind::Normal,
1 => return GeneratorResumeKind::Throw,
2 => return GeneratorResumeKind::Return,
_ => unreachable!("generator kind must be a integer between 1..=2, got {value}"),
}
}
unreachable!("generator kind must be a integer type")
}
}