Skip to main content

boa_engine/object/shape/
mod.rs

1//! Implements object shapes.
2
3pub(crate) mod property_table;
4mod root_shape;
5pub(crate) mod shared_shape;
6pub(crate) mod slot;
7pub(crate) mod unique_shape;
8
9pub use root_shape::RootShape;
10pub use shared_shape::SharedShape;
11pub(crate) use unique_shape::UniqueShape;
12
13use std::fmt::Debug;
14
15use boa_gc::{Finalize, Trace};
16
17use crate::property::PropertyKey;
18
19use self::{
20    shared_shape::{TransitionKey, WeakSharedShape},
21    slot::Slot,
22    unique_shape::WeakUniqueShape,
23};
24
25use super::JsPrototype;
26
27/// Action to be performed after a property attribute change
28//
29// Example: of { get/set x() { ... }, y: ... } into { x: ..., y: ... }
30//
31//                 0       1       2
32//    Storage: | get x | set x |   y   |
33//
34// We delete at position of x which is index 0 (it spans two elements) + 1:
35//
36//                 0      1
37//    Storage: |   x  |   y   |
38pub(crate) enum ChangeTransitionAction {
39    /// Do nothing to storage.
40    Nothing,
41
42    /// Remove element at (index + 1) from storage.
43    Remove,
44
45    /// Insert element at (index + 1) into storage.
46    Insert,
47}
48
49/// The result of a change property attribute transition.
50pub(crate) struct ChangeTransition<T> {
51    /// The shape after transition.
52    pub(crate) shape: T,
53
54    /// The needed action to be performed after transition to the object storage.
55    pub(crate) action: ChangeTransitionAction,
56}
57
58/// The internal representation of [`Shape`].
59#[derive(Debug, Trace, Finalize, Clone)]
60enum Inner {
61    Unique(UniqueShape),
62    Shared(SharedShape),
63}
64
65/// Represents the shape of an object.
66#[derive(Debug, Trace, Finalize, Clone)]
67pub struct Shape {
68    inner: Inner,
69}
70
71impl Default for Shape {
72    #[inline]
73    fn default() -> Self {
74        UniqueShape::default().into()
75    }
76}
77
78impl Shape {
79    /// The max transition count of a [`SharedShape`] from the root node,
80    /// before the shape will be converted into a [`UniqueShape`]
81    ///
82    /// NOTE: This only applies to [`SharedShape`].
83    const TRANSITION_COUNT_MAX: u16 = 1024;
84
85    /// Returns `true` if it's a shared shape, `false` otherwise.
86    #[inline]
87    #[must_use]
88    pub const fn is_shared(&self) -> bool {
89        matches!(self.inner, Inner::Shared(_))
90    }
91
92    /// Returns `true` if it's a unique shape, `false` otherwise.
93    #[inline]
94    #[must_use]
95    pub const fn is_unique(&self) -> bool {
96        matches!(self.inner, Inner::Unique(_))
97    }
98
99    pub(crate) const fn as_unique(&self) -> Option<&UniqueShape> {
100        if let Inner::Unique(shape) = &self.inner {
101            return Some(shape);
102        }
103        None
104    }
105
106    /// Create an insert property transitions returning the new transitioned [`Shape`].
107    ///
108    /// NOTE: This assumes that there is no property with the given key!
109    pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self {
110        match &self.inner {
111            Inner::Shared(shape) => {
112                let shape = shape.insert_property_transition(key);
113                if shape.transition_count() >= Self::TRANSITION_COUNT_MAX {
114                    return shape.to_unique().into();
115                }
116                shape.into()
117            }
118            Inner::Unique(shape) => shape.insert_property_transition(key).into(),
119        }
120    }
121
122    /// Create a change attribute property transitions returning [`ChangeTransition`] containing the new [`Shape`]
123    /// and actions to be performed
124    ///
125    /// NOTE: This assumes that there already is a property with the given key!
126    pub(crate) fn change_attributes_transition(
127        &self,
128        key: TransitionKey,
129    ) -> ChangeTransition<Self> {
130        match &self.inner {
131            Inner::Shared(shape) => {
132                let change_transition = shape.change_attributes_transition(key);
133                let shape =
134                    if change_transition.shape.transition_count() >= Self::TRANSITION_COUNT_MAX {
135                        change_transition.shape.to_unique().into()
136                    } else {
137                        change_transition.shape.into()
138                    };
139                ChangeTransition {
140                    shape,
141                    action: change_transition.action,
142                }
143            }
144            Inner::Unique(shape) => shape.change_attributes_transition(&key),
145        }
146    }
147
148    /// Remove a property property from the [`Shape`] returning the new transitioned [`Shape`].
149    ///
150    /// NOTE: This assumes that there already is a property with the given key!
151    pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self {
152        match &self.inner {
153            Inner::Shared(shape) => {
154                let shape = shape.remove_property_transition(key);
155                if shape.transition_count() >= Self::TRANSITION_COUNT_MAX {
156                    return shape.to_unique().into();
157                }
158                shape.into()
159            }
160            Inner::Unique(shape) => shape.remove_property_transition(key).into(),
161        }
162    }
163
164    /// Create a prototype transitions returning the new transitioned [`Shape`].
165    pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self {
166        match &self.inner {
167            Inner::Shared(shape) => {
168                let shape = shape.change_prototype_transition(prototype);
169                if shape.transition_count() >= Self::TRANSITION_COUNT_MAX {
170                    return shape.to_unique().into();
171                }
172                shape.into()
173            }
174            Inner::Unique(shape) => shape.change_prototype_transition(prototype).into(),
175        }
176    }
177
178    /// Get the [`JsPrototype`] of the [`Shape`].
179    #[must_use]
180    pub fn prototype(&self) -> JsPrototype {
181        match &self.inner {
182            Inner::Shared(shape) => shape.prototype(),
183            Inner::Unique(shape) => shape.prototype(),
184        }
185    }
186
187    /// Lookup a property in the shape
188    #[inline]
189    pub(crate) fn lookup(&self, key: &PropertyKey) -> Option<Slot> {
190        match &self.inner {
191            Inner::Shared(shape) => shape.lookup(key),
192            Inner::Unique(shape) => shape.lookup(key),
193        }
194    }
195
196    /// Returns the keys of the [`Shape`], in insertion order.
197    #[inline]
198    #[must_use]
199    pub fn keys(&self) -> Vec<PropertyKey> {
200        match &self.inner {
201            Inner::Shared(shape) => shape.keys(),
202            Inner::Unique(shape) => shape.keys(),
203        }
204    }
205
206    /// Return location in memory of the [`Shape`].
207    #[inline]
208    #[must_use]
209    pub fn to_addr_usize(&self) -> usize {
210        match &self.inner {
211            Inner::Shared(shape) => shape.to_addr_usize(),
212            Inner::Unique(shape) => shape.to_addr_usize(),
213        }
214    }
215}
216
217impl From<UniqueShape> for Shape {
218    fn from(shape: UniqueShape) -> Self {
219        Self {
220            inner: Inner::Unique(shape),
221        }
222    }
223}
224
225impl From<SharedShape> for Shape {
226    fn from(shape: SharedShape) -> Self {
227        Self {
228            inner: Inner::Shared(shape),
229        }
230    }
231}
232
233/// Represents a weak reaference to an object's [`Shape`].
234#[derive(Debug, Trace, Finalize, Clone, PartialEq)]
235pub(crate) enum WeakShape {
236    Unique(WeakUniqueShape),
237    Shared(WeakSharedShape),
238}
239
240impl WeakShape {
241    /// Return location in memory of the [`Shape`].
242    ///
243    /// Returns `0` if the shape has been freed.
244    #[inline]
245    #[must_use]
246    pub(crate) fn to_addr_usize(&self) -> usize {
247        self.upgrade().as_ref().map_or(0, Shape::to_addr_usize)
248    }
249
250    /// Return location in memory of the [`Shape`].
251    ///
252    /// Returns `0` if the shape has been freed.
253    #[inline]
254    #[must_use]
255    pub(crate) fn upgrade(&self) -> Option<Shape> {
256        match self {
257            WeakShape::Shared(shape) => Some(shape.upgrade()?.into()),
258            WeakShape::Unique(shape) => Some(shape.upgrade()?.into()),
259        }
260    }
261}
262
263impl From<&Shape> for WeakShape {
264    fn from(value: &Shape) -> Self {
265        match &value.inner {
266            Inner::Shared(shape) => WeakShape::Shared(shape.into()),
267            Inner::Unique(shape) => WeakShape::Unique(shape.into()),
268        }
269    }
270}