boa_engine/object/shape/shared_shape/
mod.rs1mod forward_transition;
2pub(crate) mod template;
3
4#[cfg(test)]
5mod tests;
6
7use std::{collections::hash_map::RandomState, hash::Hash};
8
9use bitflags::bitflags;
10use boa_gc::{Finalize, Gc, Trace, WeakGc, empty_trace};
11use indexmap::IndexMap;
12
13use crate::{JsObject, object::JsPrototype, property::PropertyKey};
14
15use self::forward_transition::ForwardTransition;
16
17use super::{
18 ChangeTransition, ChangeTransitionAction, Slot, UniqueShape, property_table::PropertyTable,
19 slot::SlotAttributes,
20};
21
22#[derive(Debug, Finalize, Clone, PartialEq, Eq, Hash)]
24pub(crate) struct TransitionKey {
25 pub(crate) property_key: PropertyKey,
26 pub(crate) attributes: SlotAttributes,
27}
28
29unsafe impl Trace for TransitionKey {
32 empty_trace!();
33}
34
35const INSERT_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0000;
36const CONFIGURE_PROPERTY_TRANSITION_TYPE: u8 = 0b0000_0001;
37const PROTOTYPE_TRANSITION_TYPE: u8 = 0b0000_0010;
38
39#[allow(unused)]
41const RESERVED_TRANSITION_TYPE: u8 = 0b0000_0011;
42
43bitflags! {
44 #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Finalize)]
46 pub struct ShapeFlags: u8 {
47 const TRANSITION_TYPE = 0b0000_0011;
49 }
50}
51
52impl ShapeFlags {
53 fn insert_property_transition_from(previous: Self) -> Self {
55 previous.difference(Self::TRANSITION_TYPE)
56 | Self::from_bits_retain(INSERT_PROPERTY_TRANSITION_TYPE)
57 }
58 fn configure_property_transition_from(previous: Self) -> Self {
59 previous.difference(Self::TRANSITION_TYPE)
60 | Self::from_bits_retain(CONFIGURE_PROPERTY_TRANSITION_TYPE)
61 }
62 fn prototype_transition_from(previous: Self) -> Self {
63 previous.difference(Self::TRANSITION_TYPE)
64 | Self::from_bits_retain(PROTOTYPE_TRANSITION_TYPE)
65 }
66
67 const fn is_insert_transition_type(self) -> bool {
68 self.intersection(Self::TRANSITION_TYPE).bits() == INSERT_PROPERTY_TRANSITION_TYPE
69 }
70 const fn is_prototype_transition_type(self) -> bool {
71 self.intersection(Self::TRANSITION_TYPE).bits() == PROTOTYPE_TRANSITION_TYPE
72 }
73}
74
75unsafe impl Trace for ShapeFlags {
78 empty_trace!();
79}
80
81#[derive(Debug, Trace, Finalize)]
83struct Inner {
84 forward_transitions: ForwardTransition,
86
87 property_count: u32,
89
90 prototype: JsPrototype,
92
93 #[unsafe_ignore_trace]
96 property_table: PropertyTable,
97
98 previous: Option<SharedShape>,
102
103 transition_count: u16,
105
106 flags: ShapeFlags,
108}
109
110#[derive(Debug, Trace, Finalize, Clone)]
112pub struct SharedShape {
113 inner: Gc<Inner>,
114}
115
116impl SharedShape {
117 fn property_table(&self) -> &PropertyTable {
118 &self.inner.property_table
119 }
120 fn property_count(&self) -> u32 {
122 self.inner.property_count
123 }
124 fn property_index(&self) -> u32 {
126 self.inner.property_count.saturating_sub(1)
127 }
128 #[must_use]
130 pub fn transition_count(&self) -> u16 {
131 self.inner.transition_count
132 }
133 #[must_use]
135 pub fn previous(&self) -> Option<&Self> {
136 self.inner.previous.as_ref()
137 }
138 #[must_use]
140 pub fn prototype(&self) -> JsPrototype {
141 self.inner.prototype.clone()
142 }
143 pub(crate) fn property(&self) -> (PropertyKey, Slot) {
145 let inner = self.property_table().inner().borrow();
146 let (key, slot) = inner
147 .keys
148 .get(self.property_index() as usize)
149 .expect("There should be a property");
150 (key.clone(), *slot)
151 }
152 fn flags(&self) -> ShapeFlags {
154 self.inner.flags
155 }
156 fn forward_transitions(&self) -> &ForwardTransition {
158 &self.inner.forward_transitions
159 }
160 #[must_use]
162 pub fn has_prototype(&self, prototype: &JsObject) -> bool {
163 self.inner.prototype.as_ref() == Some(prototype)
164 }
165
166 fn new(inner: Inner) -> Self {
168 Self {
169 inner: Gc::new(inner),
170 }
171 }
172
173 #[must_use]
175 pub(crate) fn root() -> Self {
176 Self::new(Inner {
177 forward_transitions: ForwardTransition::default(),
178 prototype: None,
179 property_count: 0,
180 property_table: PropertyTable::with_capacity(4),
182 previous: None,
183 flags: ShapeFlags::default(),
184 transition_count: 0,
185 })
186 }
187
188 pub(crate) fn change_prototype_transition(&self, prototype: JsPrototype) -> Self {
190 if let Some(shape) = self.forward_transitions().get_prototype(&prototype) {
191 if let Some(inner) = shape.upgrade() {
192 return Self { inner };
193 }
194
195 self.forward_transitions().prune_prototype_transitions();
196 }
197 let new_inner_shape = Inner {
198 forward_transitions: ForwardTransition::default(),
199 prototype: prototype.clone(),
200 property_table: self.property_table().clone(),
201 property_count: self.property_count(),
202 previous: Some(self.clone()),
203 transition_count: self.transition_count() + 1,
204 flags: ShapeFlags::prototype_transition_from(self.flags()),
205 };
206 let new_shape = Self::new(new_inner_shape);
207
208 self.forward_transitions()
209 .insert_prototype(prototype, &new_shape.inner);
210
211 new_shape
212 }
213
214 pub(crate) fn insert_property_transition(&self, key: TransitionKey) -> Self {
216 if let Some(shape) = self.forward_transitions().get_property(&key) {
218 if let Some(inner) = shape.upgrade() {
219 return Self { inner };
220 }
221
222 self.forward_transitions().prune_property_transitions();
223 }
224
225 let property_table = self.property_table().add_property_deep_clone_if_needed(
226 key.property_key.clone(),
227 key.attributes,
228 self.property_count(),
229 );
230 let new_inner_shape = Inner {
231 prototype: self.prototype(),
232 forward_transitions: ForwardTransition::default(),
233 property_table,
234 property_count: self.property_count() + 1,
235 previous: Some(self.clone()),
236 transition_count: self.transition_count() + 1,
237 flags: ShapeFlags::insert_property_transition_from(self.flags()),
238 };
239 let new_shape = Self::new(new_inner_shape);
240
241 self.forward_transitions()
242 .insert_property(key, &new_shape.inner);
243
244 new_shape
245 }
246
247 pub(crate) fn change_attributes_transition(
249 &self,
250 key: TransitionKey,
251 ) -> ChangeTransition<Self> {
252 let slot = self.property_table().get_expect(&key.property_key);
253
254 if let Some(shape) = self.forward_transitions().get_property(&key) {
256 if let Some(inner) = shape.upgrade() {
257 let action = if slot.attributes.width_match(key.attributes) {
258 ChangeTransitionAction::Nothing
259 } else if slot.attributes.is_accessor_descriptor() {
260 ChangeTransitionAction::Remove
262 } else {
263 ChangeTransitionAction::Insert
265 };
266
267 return ChangeTransition {
268 shape: Self { inner },
269 action,
270 };
271 }
272
273 self.forward_transitions().prune_property_transitions();
274 }
275
276 if slot.attributes.width_match(key.attributes) {
278 let property_table = self.property_table().deep_clone_all();
279 property_table.set_attributes_at_index(&key.property_key, key.attributes);
280 let inner_shape = Inner {
281 forward_transitions: ForwardTransition::default(),
282 prototype: self.prototype(),
283 property_table,
284 property_count: self.property_count(),
285 previous: Some(self.clone()),
286 transition_count: self.transition_count() + 1,
287 flags: ShapeFlags::configure_property_transition_from(self.flags()),
288 };
289 let shape = Self::new(inner_shape);
290
291 self.forward_transitions()
292 .insert_property(key, &shape.inner);
293
294 return ChangeTransition {
295 shape,
296 action: ChangeTransitionAction::Nothing,
297 };
298 }
299
300 let (mut base, prototype, transitions) = self.rollback_before(&key.property_key);
302
303 if let Some(prototype) = prototype {
305 base = base.change_prototype_transition(prototype);
306 }
307
308 base = base.insert_property_transition(key);
310
311 for (property_key, attributes) in transitions.into_iter().rev() {
313 let transition = TransitionKey {
314 property_key,
315 attributes,
316 };
317 base = base.insert_property_transition(transition);
318 }
319
320 let action = if slot.attributes.is_accessor_descriptor() {
322 ChangeTransitionAction::Remove
324 } else {
325 ChangeTransitionAction::Insert
327 };
328
329 ChangeTransition {
330 shape: base,
331 action,
332 }
333 }
334
335 fn rollback_before(
360 &self,
361 key: &PropertyKey,
362 ) -> (
363 Self,
364 Option<JsPrototype>,
365 IndexMap<PropertyKey, SlotAttributes>,
366 ) {
367 let mut prototype = None;
368 let mut transitions: IndexMap<PropertyKey, SlotAttributes, RandomState> =
369 IndexMap::default();
370
371 let mut current = Some(self);
372 let base = loop {
373 let Some(current_shape) = current else {
374 unreachable!("The chain should have insert transition type!")
375 };
376
377 if current_shape.flags().is_prototype_transition_type() {
379 if prototype.is_none() {
380 prototype = Some(current_shape.prototype().clone());
381 }
382
383 current = current_shape.previous();
385 continue;
386 }
387
388 let (current_property_key, slot) = current_shape.property();
389
390 if current_shape.flags().is_insert_transition_type() && ¤t_property_key == key {
391 let base = if let Some(base) = current_shape.previous() {
392 base.clone()
393 } else {
394 current_shape.clone()
396 };
397 break base;
398 }
399
400 if ¤t_property_key != key {
403 transitions
405 .entry(current_property_key)
406 .or_insert(slot.attributes);
407 }
408
409 current = current_shape.previous();
410 };
411
412 (base, prototype, transitions)
413 }
414
415 pub(crate) fn remove_property_transition(&self, key: &PropertyKey) -> Self {
417 let (mut base, prototype, transitions) = self.rollback_before(key);
418
419 if let Some(prototype) = prototype {
421 base = base.change_prototype_transition(prototype);
422 }
423
424 for (property_key, attributes) in transitions.into_iter().rev() {
425 let transition = TransitionKey {
426 property_key,
427 attributes,
428 };
429 base = base.insert_property_transition(transition);
430 }
431
432 base
433 }
434
435 pub(crate) fn lookup(&self, key: &PropertyKey) -> Option<Slot> {
437 let property_count = self.property_count();
438 if property_count == 0 {
439 return None;
440 }
441
442 let property_table_inner = self.property_table().inner().borrow();
443 if let Some((property_table_index, slot)) = property_table_inner.map.get(key)
445 && *property_table_index < self.property_count()
446 {
447 return Some(*slot);
448 }
449 None
450 }
451
452 pub(crate) fn keys(&self) -> Vec<PropertyKey> {
454 let property_table = self.property_table().inner().borrow();
455 property_table.keys_cloned_n(self.property_count())
456 }
457
458 pub(crate) fn to_unique(&self) -> UniqueShape {
460 UniqueShape::new(
461 self.prototype(),
462 self.property_table()
463 .inner()
464 .borrow()
465 .clone_count(self.property_count()),
466 )
467 }
468
469 pub(crate) fn to_addr_usize(&self) -> usize {
471 let ptr: *const _ = self.inner.as_ref();
472 ptr as usize
473 }
474}
475
476#[derive(Debug, Trace, Finalize, Clone, PartialEq)]
478pub(crate) struct WeakSharedShape {
479 inner: WeakGc<Inner>,
480}
481
482impl WeakSharedShape {
483 #[inline]
486 #[must_use]
487 pub(crate) fn upgrade(&self) -> Option<SharedShape> {
488 Some(SharedShape {
489 inner: self.inner.upgrade()?,
490 })
491 }
492}
493
494impl From<&SharedShape> for WeakSharedShape {
495 fn from(value: &SharedShape) -> Self {
496 WeakSharedShape {
497 inner: WeakGc::new(&value.inner),
498 }
499 }
500}