mod forward_transition;
pub(crate) mod template;
#[cfg(test)]
mod tests;
use std::{collections::hash_map::RandomState, hash::Hash};
use bitflags::bitflags;
use boa_gc::{empty_trace, Finalize, Gc, Trace};
use indexmap::IndexMap;
use crate::{object::JsPrototype, property::PropertyKey, JsObject};
use self::forward_transition::ForwardTransition;
use super::{
property_table::PropertyTable, slot::SlotAttributes, ChangeTransition, ChangeTransitionAction,
Slot, UniqueShape,
};
#[derive(Debug, Finalize, Clone, PartialEq, Eq, Hash)]
pub(crate) struct TransitionKey {
pub(crate) property_key: PropertyKey,
pub(crate) attributes: SlotAttributes,
}
unsafe impl Trace for TransitionKey {
empty_trace!();
}
const INSERT_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0000;
const CONFIGURE_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0001;
const PROTOTYPE_TRANSITION_TYPE: u8 = 0b0000_0010;
#[allow(unused)]
const RESEREVED_TRANSITION_TYPE: u8 = 0b0000_0011;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Finalize)]
pub struct ShapeFlags: u8 {
const TRANSITION_TYPE = 0b0000_0011;
}
}
impl Default for ShapeFlags {
fn default() -> Self {
Self::empty()
}
}
impl ShapeFlags {
fn insert_property_transition_from(previous: Self) -> Self {
previous.difference(Self::TRANSITION_TYPE)
| Self::from_bits_retain(INSERT_PROPERTY_TRANSITION_TYPE)
}
fn configure_property_transition_from(previous: Self) -> Self {
previous.difference(Self::TRANSITION_TYPE)
| Self::from_bits_retain(CONFIGURE_PROPERTY_TRANSITION_TYPE)
}
fn prototype_transition_from(previous: Self) -> Self {
previous.difference(Self::TRANSITION_TYPE)
| Self::from_bits_retain(PROTOTYPE_TRANSITION_TYPE)
}
const fn is_insert_transition_type(self) -> bool {
self.intersection(Self::TRANSITION_TYPE).bits() == INSERT_PROPERTY_TRANSITION_TYPE
}
const fn is_prototype_transition_type(self) -> bool {
self.intersection(Self::TRANSITION_TYPE).bits() == PROTOTYPE_TRANSITION_TYPE
}
}
unsafe impl Trace for ShapeFlags {
empty_trace!();
}
#[derive(Debug, Trace, Finalize)]
struct Inner {
forward_transitions: ForwardTransition,
property_count: u32,
prototype: JsPrototype,
#[unsafe_ignore_trace]
property_table: PropertyTable,
previous: Option<SharedShape>,
transition_count: u16,
flags: ShapeFlags,
}
#[derive(Debug, Trace, Finalize, Clone)]
pub struct SharedShape {
inner: Gc<Inner>,
}
impl SharedShape {
fn property_table(&self) -> &PropertyTable {
&self.inner.property_table
}
fn property_count(&self) -> u32 {
self.inner.property_count
}
fn property_index(&self) -> u32 {
self.inner.property_count.saturating_sub(1)
}
pub fn transition_count(&self) -> u16 {
self.inner.transition_count
}
pub fn previous(&self) -> Option<&Self> {
self.inner.previous.as_ref()
}
pub fn prototype(&self) -> JsPrototype {
self.inner.prototype.clone()
}
pub(crate) fn property(&self) -> (PropertyKey, Slot) {
let inner = self.property_table().inner().borrow();
let (key, slot) = inner
.keys
.get(self.property_index() as usize)
.expect("There should be a property");
(key.clone(), *slot)
}
fn flags(&self) -> ShapeFlags {
self.inner.flags
}
fn forward_transitions(&self) -> &ForwardTransition {
&self.inner.forward_transitions
}
pub fn has_prototype(&self, prototype: &JsObject) -> bool {
self.inner
.prototype
.as_ref()
.map_or(false, |this| this == prototype)
}
fn new(inner: Inner) -> Self {
Self {
inner: Gc::new(inner),
}
}
#[must_use]
pub(crate) fn root() -> Self {
Self::new(Inner {
forward_transitions: ForwardTransition::default(),
prototype: None,
property_count: 0,
property_table: PropertyTable::default(),
previous: None,
flags: ShapeFlags::default(),
transition_count: 0,
})
}
pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self {
if let Some(shape) = self.forward_transitions().get_prototype(&prototype) {
if let Some(inner) = shape.upgrade() {
return Self { inner };
}
self.forward_transitions().prune_prototype_transitions();
}
let new_inner_shape = Inner {
forward_transitions: ForwardTransition::default(),
prototype: prototype.clone(),
property_table: self.property_table().clone(),
property_count: self.property_count(),
previous: Some(self.clone()),
transition_count: self.transition_count() + 1,
flags: ShapeFlags::prototype_transition_from(self.flags()),
};
let new_shape = Self::new(new_inner_shape);
self.forward_transitions()
.insert_prototype(prototype, &new_shape.inner);
new_shape
}
pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self {
if let Some(shape) = self.forward_transitions().get_property(&key) {
if let Some(inner) = shape.upgrade() {
return Self { inner };
}
self.forward_transitions().prune_property_transitions();
}
let property_table = self.property_table().add_property_deep_clone_if_needed(
key.property_key.clone(),
key.attributes,
self.property_count(),
);
let new_inner_shape = Inner {
prototype: self.prototype(),
forward_transitions: ForwardTransition::default(),
property_table,
property_count: self.property_count() + 1,
previous: Some(self.clone()),
transition_count: self.transition_count() + 1,
flags: ShapeFlags::insert_property_transition_from(self.flags()),
};
let new_shape = Self::new(new_inner_shape);
self.forward_transitions()
.insert_property(key, &new_shape.inner);
new_shape
}
pub(crate) fn change_attributes_transition(
&self,
key: TransitionKey,
) -> ChangeTransition<Self> {
let slot = self.property_table().get_expect(&key.property_key);
if let Some(shape) = self.forward_transitions().get_property(&key) {
if let Some(inner) = shape.upgrade() {
let action = if slot.attributes.width_match(key.attributes) {
ChangeTransitionAction::Nothing
} else if slot.attributes.is_accessor_descriptor() {
ChangeTransitionAction::Remove
} else {
ChangeTransitionAction::Insert
};
return ChangeTransition {
shape: Self { inner },
action,
};
}
self.forward_transitions().prune_property_transitions();
}
if slot.attributes.width_match(key.attributes) {
let property_table = self.property_table().deep_clone_all();
property_table.set_attributes_at_index(&key.property_key, key.attributes);
let inner_shape = Inner {
forward_transitions: ForwardTransition::default(),
prototype: self.prototype(),
property_table,
property_count: self.property_count(),
previous: Some(self.clone()),
transition_count: self.transition_count() + 1,
flags: ShapeFlags::configure_property_transition_from(self.flags()),
};
let shape = Self::new(inner_shape);
self.forward_transitions()
.insert_property(key, &shape.inner);
return ChangeTransition {
shape,
action: ChangeTransitionAction::Nothing,
};
}
let (mut base, prototype, transitions) = self.rollback_before(&key.property_key);
if let Some(prototype) = prototype {
base = base.change_prototype_transition(prototype);
}
base = base.insert_property_transition(key);
for (property_key, attributes) in transitions.into_iter().rev() {
let transition = TransitionKey {
property_key,
attributes,
};
base = base.insert_property_transition(transition);
}
let action = if slot.attributes.is_accessor_descriptor() {
ChangeTransitionAction::Remove
} else {
ChangeTransitionAction::Insert
};
ChangeTransition {
shape: base,
action,
}
}
fn rollback_before(
&self,
key: &PropertyKey,
) -> (
Self,
Option<JsPrototype>,
IndexMap<PropertyKey, SlotAttributes>,
) {
let mut prototype = None;
let mut transitions: IndexMap<PropertyKey, SlotAttributes, RandomState> =
IndexMap::default();
let mut current = Some(self);
let base = loop {
let Some(current_shape) = current else {
unreachable!("The chain should have insert transition type!")
};
if current_shape.flags().is_prototype_transition_type() {
if prototype.is_none() {
prototype = Some(current_shape.prototype().clone());
}
current = current_shape.previous();
continue;
}
let (current_property_key, slot) = current_shape.property();
if current_shape.flags().is_insert_transition_type() && ¤t_property_key == key {
let base = if let Some(base) = current_shape.previous() {
base.clone()
} else {
current_shape.clone()
};
break base;
}
if ¤t_property_key != key {
transitions
.entry(current_property_key)
.or_insert(slot.attributes);
}
current = current_shape.previous();
};
(base, prototype, transitions)
}
pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self {
let (mut base, prototype, transitions) = self.rollback_before(key);
if let Some(prototype) = prototype {
base = base.change_prototype_transition(prototype);
}
for (property_key, attributes) in transitions.into_iter().rev() {
let transition = TransitionKey {
property_key,
attributes,
};
base = base.insert_property_transition(transition);
}
base
}
pub(crate) fn lookup(&self, key: &PropertyKey) -> Option<Slot> {
let property_count = self.property_count();
if property_count == 0 {
return None;
}
let property_table_inner = self.property_table().inner().borrow();
if let Some((property_table_index, slot)) = property_table_inner.map.get(key) {
if *property_table_index < self.property_count() {
return Some(*slot);
}
}
None
}
pub(crate) fn keys(&self) -> Vec<PropertyKey> {
let property_table = self.property_table().inner().borrow();
property_table.keys_cloned_n(self.property_count())
}
pub(crate) fn to_unique(&self) -> UniqueShape {
UniqueShape::new(
self.prototype(),
self.property_table()
.inner()
.borrow()
.clone_count(self.property_count()),
)
}
pub(crate) fn to_addr_usize(&self) -> usize {
let ptr: *const _ = self.inner.as_ref();
ptr as usize
}
}