Skip to main content

oxide_core/
shape.rs

1//! Shapes: the transition tree, dictionary mode and prototype validity cells.
2//!
3//! A transition shape describes one property added on top of its parent; the
4//! tree has one root per `(prototype, kind)`. Lookup walks `parent` for eight
5//! properties or fewer and otherwise uses a table built on first lookup.
6//! Transition shapes are immutable and shared; reconfiguring a property or
7//! changing the prototype replays the object's keys from another root, so the
8//! slots keep their positions.
9//!
10//! Dictionary mode gives an object an unshared shape with its own hash table
11//! and insertion-ordered entry list. It is entered on deletion, past
12//! [`DICTIONARY_THRESHOLD`] properties, and always for the global object.
13//!
14//! Every object used as a prototype has a `PrototypeInfo`: a lazily created
15//! validity cell and the list of prototype objects that inherit from it. Any
16//! change to a prototype object clears its cell and, through the dependents
17//! lists, every cell downstream. A shape caches its prototype's cell; the
18//! prototype IC records `(holder, slot, cell)` and tests one boolean.
19
20use std::cell::OnceCell;
21use std::fmt;
22
23use crate::heap::arena::Record;
24use crate::heap::atom::{KeyKind, PropertyKey};
25use crate::heap::hash::FxHashMap;
26use crate::heap::object::{NONE, ObjectKind, flags};
27use crate::heap::{Heap, HeapError, Need};
28use crate::value::{ObjectId, ShapeId, ValidityCellId, Value};
29
30/// Past this many properties a transition shape builds a lookup table.
31pub const TABLE_THRESHOLD: u32 = 8;
32/// Adding a property beyond this count moves the object to dictionary mode.
33pub const DICTIONARY_THRESHOLD: u32 = 64;
34/// The always-valid cell of shapes without a prototype.
35pub const STATIC_CELL: ValidityCellId = ValidityCellId(0);
36
37#[derive(Clone, Copy, PartialEq, Eq, Hash)]
38pub struct PropertyFlags(u8);
39
40impl PropertyFlags {
41    pub const WRITABLE: u8 = 1 << 0;
42    pub const ENUMERABLE: u8 = 1 << 1;
43    pub const CONFIGURABLE: u8 = 1 << 2;
44    pub const ACCESSOR: u8 = 1 << 3;
45    /// A pending closure's `length`, `name` or `prototype`: the key and its
46    /// attributes are in the shape, the value is not yet in a slot (see
47    /// [`Heap::pending_closure_shape`]). Never read or written before the
48    /// closure is materialized.
49    pub const AUTOINIT: u8 = 1 << 4;
50    /// Writable, enumerable, configurable data property.
51    pub const DEFAULT: PropertyFlags = PropertyFlags::data(true, true, true);
52
53    pub const fn data(writable: bool, enumerable: bool, configurable: bool) -> Self {
54        PropertyFlags(
55            (writable as u8 * Self::WRITABLE)
56                | (enumerable as u8 * Self::ENUMERABLE)
57                | (configurable as u8 * Self::CONFIGURABLE),
58        )
59    }
60
61    pub const fn accessor(enumerable: bool, configurable: bool) -> Self {
62        PropertyFlags(
63            Self::ACCESSOR
64                | (enumerable as u8 * Self::ENUMERABLE)
65                | (configurable as u8 * Self::CONFIGURABLE),
66        )
67    }
68
69    pub const fn bits(self) -> u8 {
70        self.0
71    }
72
73    pub const fn has(self, flag: u8) -> bool {
74        self.0 & flag != 0
75    }
76
77    pub const fn with(self, flag: u8) -> Self {
78        PropertyFlags(self.0 | flag)
79    }
80
81    /// The slots a property with these flags occupies: two for an accessor
82    /// (getter, then setter), one for a data property.
83    pub const fn width(self) -> u32 {
84        if self.has(Self::ACCESSOR) { 2 } else { 1 }
85    }
86}
87
88impl fmt::Debug for PropertyFlags {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        let flag = |bit, name| if self.has(bit) { name } else { "-" };
91        write!(
92            f,
93            "{}{}{}{}{}",
94            flag(Self::AUTOINIT, "i"),
95            flag(Self::ACCESSOR, "a"),
96            flag(Self::WRITABLE, "w"),
97            flag(Self::ENUMERABLE, "e"),
98            flag(Self::CONFIGURABLE, "c")
99        )
100    }
101}
102
103/// A shape's outgoing transitions: a vector for up to four entries, a map
104/// beyond. Entries are weak: the collector drops those whose child died.
105#[derive(Default)]
106pub enum Transitions {
107    #[default]
108    None,
109    Small(Vec<(PropertyKey, PropertyFlags, ShapeId)>),
110    Map(Box<FxHashMap<(PropertyKey, PropertyFlags), ShapeId>>),
111}
112
113const SMALL_TRANSITIONS: usize = 4;
114
115impl Transitions {
116    #[inline]
117    pub fn find(&self, key: PropertyKey, flags: PropertyFlags) -> Option<ShapeId> {
118        match self {
119            Transitions::None => None,
120            Transitions::Small(entries) => entries
121                .iter()
122                .find(|(entry_key, entry_flags, _)| *entry_key == key && *entry_flags == flags)
123                .map(|entry| entry.2),
124            Transitions::Map(map) => map.get(&(key, flags)).copied(),
125        }
126    }
127
128    fn insert(&mut self, key: PropertyKey, flags: PropertyFlags, child: ShapeId) {
129        match self {
130            Transitions::None => *self = Transitions::Small(vec![(key, flags, child)]),
131            Transitions::Small(entries) if entries.len() < SMALL_TRANSITIONS => {
132                entries.push((key, flags, child))
133            }
134            Transitions::Small(entries) => {
135                let mut map: FxHashMap<_, _> =
136                    entries.drain(..).map(|(k, f, c)| ((k, f), c)).collect();
137                map.insert((key, flags), child);
138                *self = Transitions::Map(Box::new(map));
139            }
140            Transitions::Map(map) => {
141                map.insert((key, flags), child);
142            }
143        }
144    }
145
146    pub fn retain(&mut self, mut keep: impl FnMut(ShapeId) -> bool) {
147        match self {
148            Transitions::None => {}
149            Transitions::Small(entries) => entries.retain(|entry| keep(entry.2)),
150            Transitions::Map(map) => map.retain(|_, child| keep(*child)),
151        }
152    }
153
154    pub fn len(&self) -> usize {
155        match self {
156            Transitions::None => 0,
157            Transitions::Small(entries) => entries.len(),
158            Transitions::Map(map) => map.len(),
159        }
160    }
161
162    pub fn is_empty(&self) -> bool {
163        self.len() == 0
164    }
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub struct DictEntry {
169    pub key: PropertyKey,
170    pub flags: PropertyFlags,
171    /// The value slot, or [`NONE`] for a deleted entry.
172    pub slot: u32,
173}
174
175/// The unshared table of a dictionary-mode object.
176#[derive(Default)]
177pub struct Dictionary {
178    map: FxHashMap<PropertyKey, u32>,
179    entries: Vec<DictEntry>,
180    free_slots: Vec<u32>,
181    next_slot: u32,
182}
183
184impl Dictionary {
185    fn from_entries(entries: impl IntoIterator<Item = (PropertyKey, PropertyFlags, u32)>) -> Self {
186        let mut dictionary = Dictionary::default();
187        for (key, flags, slot) in entries {
188            dictionary.map.insert(key, dictionary.entries.len() as u32);
189            dictionary.entries.push(DictEntry { key, flags, slot });
190            dictionary.next_slot = dictionary.next_slot.max(slot + flags.width());
191        }
192        dictionary
193    }
194
195    #[inline]
196    pub fn find(&self, key: PropertyKey) -> Option<(u32, PropertyFlags)> {
197        let entry = &self.entries[*self.map.get(&key)? as usize];
198        Some((entry.slot, entry.flags))
199    }
200
201    /// The slot the next insertion with `flags` will use. An accessor takes
202    /// two fresh slots; a data property reuses a freed one first.
203    fn next_slot(&self, flags: PropertyFlags) -> u32 {
204        match self.free_slots.last() {
205            Some(&slot) if flags.width() == 1 => slot,
206            _ => self.next_slot,
207        }
208    }
209
210    fn insert(&mut self, key: PropertyKey, flags: PropertyFlags) -> u32 {
211        let slot = match self.free_slots.pop() {
212            Some(slot) if flags.width() == 1 => slot,
213            free => {
214                self.free_slots.extend(free);
215                self.next_slot += flags.width();
216                self.next_slot - flags.width()
217            }
218        };
219        self.map.insert(key, self.entries.len() as u32);
220        self.entries.push(DictEntry { key, flags, slot });
221        slot
222    }
223
224    fn remove(&mut self, key: PropertyKey) -> Option<u32> {
225        let index = self.map.remove(&key)?;
226        let entry = &mut self.entries[index as usize];
227        let slot = entry.slot;
228        entry.slot = NONE;
229        self.free_slots.extend(slot..slot + entry.flags.width());
230        if self.entries.len() > 16 && self.entries.len() > 2 * self.map.len() {
231            self.entries.retain(|entry| entry.slot != NONE);
232            for (index, entry) in self.entries.iter().enumerate() {
233                self.map.insert(entry.key, index as u32);
234            }
235        }
236        Some(slot)
237    }
238
239    /// Gives `key` new flags of a different width, keeping its place in the
240    /// entry order: its old slots are freed and new ones taken. Returns the
241    /// new slot.
242    fn reslot(&mut self, key: PropertyKey, flags: PropertyFlags) -> Option<u32> {
243        let index = *self.map.get(&key)? as usize;
244        let old = self.entries[index];
245        self.free_slots
246            .extend(old.slot..old.slot + old.flags.width());
247        let slot = match self.free_slots.pop() {
248            Some(slot) if flags.width() == 1 => slot,
249            free => {
250                self.free_slots.extend(free);
251                self.next_slot += flags.width();
252                self.next_slot - flags.width()
253            }
254        };
255        self.entries[index] = DictEntry { key, flags, slot };
256        Some(slot)
257    }
258
259    fn set_flags(&mut self, key: PropertyKey, flags: PropertyFlags) -> bool {
260        match self.map.get(&key) {
261            Some(&index) => {
262                self.entries[index as usize].flags = flags;
263                true
264            }
265            None => false,
266        }
267    }
268
269    /// Live entries in insertion order.
270    pub fn entries(&self) -> impl Iterator<Item = &DictEntry> {
271        self.entries.iter().filter(|entry| entry.slot != NONE)
272    }
273
274    pub fn len(&self) -> usize {
275        self.map.len()
276    }
277
278    pub fn is_empty(&self) -> bool {
279        self.map.is_empty()
280    }
281
282    /// Moves the present keys of `front` to the start of the insertion
283    /// order, in that order; the rest keep their relative order. Slots are
284    /// unchanged.
285    fn reorder(&mut self, front: &[PropertyKey]) {
286        let mut entries = Vec::with_capacity(self.map.len());
287        for &key in front {
288            if let Some(&index) = self.map.get(&key) {
289                entries.push(self.entries[index as usize]);
290            }
291        }
292        for entry in &self.entries {
293            if entry.slot != NONE && !front.contains(&entry.key) {
294                entries.push(*entry);
295            }
296        }
297        self.map.clear();
298        for (index, entry) in entries.iter().enumerate() {
299            self.map.insert(entry.key, index as u32);
300        }
301        self.entries = entries;
302    }
303}
304
305pub struct Shape {
306    /// Transition-tree parent; `None` for roots and dictionary shapes.
307    pub parent: Option<ShapeId>,
308    /// The property this node adds (meaningless when `count == 0`).
309    pub key: PropertyKey,
310    pub flags: PropertyFlags,
311    pub slot: u32,
312    /// Properties up to and including this node.
313    pub count: u32,
314    /// Slots used up to and including this node: `count` plus one per
315    /// accessor, whose getter and setter take two.
316    pub width: u32,
317    pub prototype: Option<ObjectId>,
318    pub kind: ObjectKind,
319    pub transitions: Transitions,
320    /// Key → (slot, flags), built on the first lookup past
321    /// [`TABLE_THRESHOLD`] properties.
322    pub table: OnceCell<Box<FxHashMap<PropertyKey, (u32, PropertyFlags)>>>,
323    /// The prototype's validity cell as last seen, or [`NONE`].
324    pub validity: ValidityCellId,
325    pub dictionary: Option<Box<Dictionary>>,
326}
327
328impl Shape {
329    fn root(prototype: Option<ObjectId>, kind: ObjectKind) -> Self {
330        Shape {
331            parent: None,
332            key: PropertyKey::index(0).unwrap(),
333            flags: PropertyFlags::DEFAULT,
334            slot: NONE,
335            count: 0,
336            width: 0,
337            prototype,
338            kind,
339            transitions: Transitions::None,
340            table: OnceCell::new(),
341            validity: ValidityCellId(NONE),
342            dictionary: None,
343        }
344    }
345
346    pub fn is_dictionary(&self) -> bool {
347        self.dictionary.is_some()
348    }
349
350    /// Whether the lookup table has been built.
351    pub fn has_table(&self) -> bool {
352        self.table.get().is_some()
353    }
354}
355
356impl Record for Shape {
357    fn vacant() -> Self {
358        Shape::root(None, ObjectKind::Free)
359    }
360}
361
362#[derive(Clone, Copy, Debug)]
363pub struct ValidityCell {
364    pub valid: bool,
365}
366
367impl Record for ValidityCell {
368    fn vacant() -> Self {
369        ValidityCell { valid: false }
370    }
371}
372
373/// Per-prototype bookkeeping. The table holding it is weak in its key and in
374/// `dependents`; the collector prunes dead objects after marking.
375#[derive(Default, Debug)]
376pub struct PrototypeInfo {
377    pub cell: Option<ValidityCellId>,
378    /// Prototype objects whose `[[Prototype]]` is this object.
379    pub dependents: Vec<ObjectId>,
380}
381
382impl Heap {
383    // ---- shape construction (raw: callers have passed a safepoint) ----
384
385    #[inline]
386    pub(crate) fn root_shape(&mut self, prototype: Option<ObjectId>, kind: ObjectKind) -> ShapeId {
387        let key = (prototype.map_or(NONE, |p| p.0), kind);
388        if let Some((cached, shape)) = self.last_root
389            && cached == key
390        {
391            return shape;
392        }
393        self.root_shape_slow(prototype, kind, key)
394    }
395
396    #[inline(never)]
397    fn root_shape_slow(
398        &mut self,
399        prototype: Option<ObjectId>,
400        kind: ObjectKind,
401        key: (u32, ObjectKind),
402    ) -> ShapeId {
403        if let Some(&shape) = self.shape_roots.get(&key) {
404            self.last_root = Some((key, shape));
405            return shape;
406        }
407        if let Some(prototype) = prototype {
408            self.ensure_prototype(prototype);
409        }
410        let shape = ShapeId(
411            self.shapes
412                .alloc(Shape::root(prototype, kind), &mut self.gc.room),
413        );
414        self.shape_roots.insert(key, shape);
415        self.last_root = Some((key, shape));
416        shape
417    }
418
419    pub(crate) fn dictionary_shape(
420        &mut self,
421        prototype: Option<ObjectId>,
422        kind: ObjectKind,
423        dictionary: Dictionary,
424    ) -> ShapeId {
425        if let Some(prototype) = prototype {
426            self.ensure_prototype(prototype);
427        }
428        let mut shape = Shape::root(prototype, kind);
429        shape.dictionary = Some(Box::new(dictionary));
430        ShapeId(self.shapes.alloc(shape, &mut self.gc.room))
431    }
432
433    fn transition(&mut self, shape: ShapeId, key: PropertyKey, flags: PropertyFlags) -> ShapeId {
434        let parent = self.shapes.get(shape.0);
435        if let Some(child) = parent.transitions.find(key, flags) {
436            return child;
437        }
438        let mut child = Shape::root(parent.prototype, parent.kind);
439        child.parent = Some(shape);
440        child.key = key;
441        child.flags = flags;
442        child.slot = parent.width;
443        child.count = parent.count + 1;
444        child.width = parent.width + flags.width();
445        let child = ShapeId(self.shapes.alloc(child, &mut self.gc.room));
446        self.shapes
447            .get_mut(shape.0)
448            .transitions
449            .insert(key, flags, child);
450        child
451    }
452
453    fn build_table(&self, shape: ShapeId) -> FxHashMap<PropertyKey, (u32, PropertyFlags)> {
454        let mut current = self.shapes.get(shape.0);
455        let mut table = FxHashMap::default();
456        table.reserve(current.count as usize);
457        while current.count > 0 {
458            table.insert(current.key, (current.slot, current.flags));
459            current = self
460                .shapes
461                .get(current.parent.expect("non-root shape has a parent").0);
462        }
463        table
464    }
465
466    /// Looks `key` up in one shape.
467    #[inline]
468    pub fn shape_lookup(&self, shape: ShapeId, key: PropertyKey) -> Option<(u32, PropertyFlags)> {
469        let record = self.shapes.get(shape.0);
470        if let Some(dictionary) = &record.dictionary {
471            return dictionary.find(key);
472        }
473        if record.count > TABLE_THRESHOLD {
474            return record
475                .table
476                .get_or_init(|| Box::new(self.build_table(shape)))
477                .get(&key)
478                .copied();
479        }
480        let mut current = record;
481        while current.count > 0 {
482            if current.key == key {
483                return Some((current.slot, current.flags));
484            }
485            current = self
486                .shapes
487                .get(current.parent.expect("non-root shape has a parent").0);
488        }
489        None
490    }
491
492    /// `(key, flags, slot)` of every property in insertion order.
493    pub fn shape_entries(&self, shape: ShapeId) -> Vec<(PropertyKey, PropertyFlags, u32)> {
494        let record = self.shapes.get(shape.0);
495        if let Some(dictionary) = &record.dictionary {
496            return dictionary
497                .entries()
498                .map(|entry| (entry.key, entry.flags, entry.slot))
499                .collect();
500        }
501        let mut entries = Vec::with_capacity(record.count as usize);
502        let mut current = record;
503        while current.count > 0 {
504            entries.push((current.key, current.flags, current.slot));
505            current = self
506                .shapes
507                .get(current.parent.expect("non-root shape has a parent").0);
508        }
509        entries.reverse();
510        entries
511    }
512
513    pub fn shape(&self, shape: ShapeId) -> &Shape {
514        self.shapes.get(shape.0)
515    }
516
517    pub(crate) fn prototype_of_shape(&self, shape: ShapeId) -> Option<ObjectId> {
518        self.shapes.get(shape.0).prototype
519    }
520
521    pub fn shape_of(&self, object: ObjectId) -> ShapeId {
522        self.objects.get(object.0).shape
523    }
524
525    pub fn is_dictionary(&self, object: ObjectId) -> bool {
526        self.objects.get(object.0).has(flags::DICTIONARY)
527    }
528
529    pub fn prototype_of(&self, object: ObjectId) -> Option<ObjectId> {
530        self.shapes
531            .get(self.objects.get(object.0).shape.0)
532            .prototype
533    }
534
535    // ---- properties ----
536
537    /// The slot and flags of an own named property.
538    #[inline]
539    pub fn lookup_own(&self, object: ObjectId, key: PropertyKey) -> Option<(u32, PropertyFlags)> {
540        self.shape_lookup(self.objects.get(object.0).shape, key)
541    }
542
543    /// Whether `object` is a pending closure: a function whose shape has more
544    /// slots than its range, the `AUTOINIT` entries of
545    /// [`Heap::pending_closure_shape`] not yet materialized.
546    #[inline(always)]
547    pub fn is_pending_closure(&self, object: ObjectId) -> bool {
548        let header = self.objects.get(object.0);
549        header.kind == ObjectKind::Function
550            && header.slots().len < self.shapes.get(header.shape.0).width
551    }
552
553    /// Gives a new closure, allocated with no slots, the shape reached from
554    /// the root for its prototype by adding `entries` with the `AUTOINIT`
555    /// flag: its keys and attributes without their values. Returns the shape.
556    pub(crate) fn pending_closure_shape(
557        &mut self,
558        function: ObjectId,
559        entries: &[(PropertyKey, PropertyFlags)],
560    ) -> Result<ShapeId, HeapError> {
561        debug_assert_eq!(self.objects.get(function.0).slots().len, 0);
562        let need = Need {
563            shapes: entries.len() + 1,
564            ..Need::default()
565        };
566        self.safepoint(&need, &[Value::object(function)])?;
567        let mut shape = self.root_shape(self.prototype_of(function), ObjectKind::Function);
568        for &(key, flags) in entries {
569            shape = self.transition(shape, key, flags.with(PropertyFlags::AUTOINIT));
570        }
571        self.objects.get_mut(function.0).shape = shape;
572        Ok(shape)
573    }
574
575    /// Turns a pending closure into a function with a range of `count` slots
576    /// and no properties, the root shape for its prototype, for its
577    /// properties to be added in their pending order.
578    pub(crate) fn unpend_closure(
579        &mut self,
580        function: ObjectId,
581        count: u32,
582    ) -> Result<(), HeapError> {
583        debug_assert!(self.is_pending_closure(function));
584        let need = Need {
585            shapes: 1,
586            values: [count, 0],
587            ..Need::default()
588        };
589        self.safepoint(&need, &[Value::object(function)])?;
590        let shape = self.root_shape(self.prototype_of(function), ObjectKind::Function);
591        let slots = self.values.alloc_slots(count, &mut self.gc.room);
592        let header = self.objects.get_mut(function.0);
593        header.shape = shape;
594        header.set_slots(slots);
595        if header.has(flags::USED_AS_PROTOTYPE) {
596            self.invalidate(function);
597        }
598        Ok(())
599    }
600
601    /// `values[start + slot]`: one header load, one value load.
602    #[inline]
603    #[track_caller]
604    pub fn get_slot(&self, object: ObjectId, slot: u32) -> Value {
605        let header = self.objects.get(object.0);
606        debug_assert!(
607            slot < header.slots().len,
608            "slot {slot} outside the object's range"
609        );
610        self.values.get_at(header.slots_start + slot)
611    }
612
613    #[inline]
614    #[track_caller]
615    pub fn set_slot(&mut self, object: ObjectId, slot: u32, value: Value) {
616        let header = self.objects.get(object.0);
617        debug_assert!(
618            slot < header.slots().len,
619            "slot {slot} outside the object's range"
620        );
621        self.values.set_at(header.slots_start + slot, value);
622    }
623
624    /// The value of a named property, following the prototype chain.
625    pub fn get_property(&self, object: ObjectId, key: PropertyKey) -> Option<Value> {
626        let mut current = object;
627        loop {
628            if let Some((slot, _)) = self.lookup_own(current, key) {
629                return Some(self.get_slot(current, slot));
630            }
631            current = self.prototype_of(current)?;
632        }
633    }
634
635    /// Adds a new own data property. `DuplicateKey` if it already exists.
636    #[inline]
637    pub fn add_property(
638        &mut self,
639        object: ObjectId,
640        key: PropertyKey,
641        flags: PropertyFlags,
642        value: Value,
643    ) -> Result<(), HeapError> {
644        debug_assert!(!flags.has(PropertyFlags::ACCESSOR), "add_accessor");
645        self.add_slots(object, key, flags, [value, Value::UNDEFINED])
646    }
647
648    /// Adds a new own accessor property: `getter` and `setter` take two
649    /// consecutive slots, `undefined` for an absent half. `lookup_own`
650    /// reports the getter's slot.
651    pub fn add_accessor(
652        &mut self,
653        object: ObjectId,
654        key: PropertyKey,
655        flags: PropertyFlags,
656        getter: Value,
657        setter: Value,
658    ) -> Result<(), HeapError> {
659        debug_assert!(flags.has(PropertyFlags::ACCESSOR));
660        self.add_slots(object, key, flags, [getter, setter])
661    }
662
663    #[inline]
664    fn add_slots(
665        &mut self,
666        object: ObjectId,
667        key: PropertyKey,
668        flags: PropertyFlags,
669        values: [Value; 2],
670    ) -> Result<(), HeapError> {
671        if let KeyKind::Index(_) = key.kind() {
672            self.note_prototype_index(object);
673        }
674        // Fast path: a known transition and free slots. A transition for
675        // `key` proves the current shape lacks it (shapes are immutable), so
676        // no duplicate check, and nothing is allocated.
677        let header = self.objects.get(object.0);
678        if !header.has(flags::DICTIONARY) {
679            let slots = header.slots();
680            let shape = self.shapes.get(header.shape.0);
681            let width = shape.width;
682            if width + flags.width() <= slots.len
683                && let Some(next) = shape.transitions.find(key, flags)
684            {
685                let header = self.objects.get_mut(object.0);
686                header.shape = next;
687                let prototype = header.has(flags::USED_AS_PROTOTYPE);
688                self.values.set_at(slots.start + width, values[0]);
689                if flags.width() == 2 {
690                    self.values.set_at(slots.start + width + 1, values[1]);
691                }
692                if prototype {
693                    self.invalidate(object);
694                }
695                return Ok(());
696            }
697        }
698        self.add_property_slow(object, key, flags, values)
699    }
700
701    #[inline(never)]
702    fn add_property_slow(
703        &mut self,
704        object: ObjectId,
705        key: PropertyKey,
706        flags: PropertyFlags,
707        values: [Value; 2],
708    ) -> Result<(), HeapError> {
709        debug_assert!(!self.is_pending_closure(object), "a pending closure");
710        let header = *self.objects.get(object.0);
711        if header.has(flags::DICTIONARY) {
712            return self.dictionary_add(object, key, flags, values);
713        }
714        let shape = self.shapes.get(header.shape.0);
715        let (count, width) = (shape.count, shape.width);
716        let known = shape.transitions.find(key, flags);
717        // A known transition proves the key is absent; otherwise check.
718        if known.is_none() && self.shape_lookup(header.shape, key).is_some() {
719            return Err(HeapError::DuplicateKey);
720        }
721        let keep = [Value::object(object), key.to_value(), values[0], values[1]];
722        if count >= DICTIONARY_THRESHOLD {
723            self.enter_dictionary(object, &keep[1..])?;
724            return self.dictionary_add(object, key, flags, values);
725        }
726        let slots = header.slots();
727        let needed = width + flags.width();
728        let grow = needed > slots.len;
729        let new_len = (slots.len * 2).max(needed).max(4);
730        let collections = self.gc.stats.collections;
731        if grow || known.is_none() {
732            let need = Need {
733                shapes: 1,
734                values: [if grow { new_len } else { 0 }, 0],
735                ..Need::default()
736            };
737            self.safepoint(&need, &keep)?;
738        }
739        // The object was rooted, so its shape and ranges are unchanged. A
740        // known child is reused only if no collection ran: it is reachable
741        // only through the weak transition and may have died.
742        let next = match known {
743            Some(child) if self.gc.stats.collections == collections => child,
744            _ => self.transition(header.shape, key, flags),
745        };
746        let mut slots = slots;
747        if grow {
748            slots = self.values.grow_slots(slots, new_len, &mut self.gc.room);
749        }
750        let header = self.objects.get_mut(object.0);
751        header.shape = next;
752        header.set_slots(slots);
753        let prototype = header.has(flags::USED_AS_PROTOTYPE);
754        self.values.set(slots, width, values[0]);
755        if flags.width() == 2 {
756            self.values.set(slots, width + 1, values[1]);
757        }
758        if prototype {
759            self.invalidate(object);
760        }
761        Ok(())
762    }
763
764    fn dictionary_add(
765        &mut self,
766        object: ObjectId,
767        key: PropertyKey,
768        flags: PropertyFlags,
769        values: [Value; 2],
770    ) -> Result<(), HeapError> {
771        let header = *self.objects.get(object.0);
772        let dictionary = self
773            .shapes
774            .get(header.shape.0)
775            .dictionary
776            .as_deref()
777            .expect("dictionary shape");
778        if dictionary.find(key).is_some() {
779            return Err(HeapError::DuplicateKey);
780        }
781        let slot = dictionary.next_slot(flags);
782        let end = slot + flags.width();
783        let mut slots = header.slots();
784        if end > slots.len {
785            let new_len = (slots.len * 2).max(end).max(4);
786            let need = Need {
787                values: [new_len, 0],
788                ..Need::default()
789            };
790            let keep = [Value::object(object), key.to_value(), values[0], values[1]];
791            self.safepoint(&need, &keep)?;
792            slots = self.values.grow_slots(slots, new_len, &mut self.gc.room);
793            self.objects.get_mut(object.0).set_slots(slots);
794        }
795        let dictionary = self
796            .shapes
797            .get_mut(header.shape.0)
798            .dictionary
799            .as_deref_mut()
800            .expect("dictionary shape");
801        let used = dictionary.insert(key, flags);
802        debug_assert_eq!(used, slot);
803        self.values.set(slots, slot, values[0]);
804        if flags.width() == 2 {
805            self.values.set(slots, slot + 1, values[1]);
806        }
807        self.dictionary_changed(object);
808        if header.has(flags::USED_AS_PROTOTYPE) {
809            self.invalidate(object);
810        }
811        Ok(())
812    }
813
814    /// Bumps the global generation when `object` is the global object: an
815    /// insert, remove or reflag of its dictionary moves a slot or a flag that a
816    /// global cache entry may hold.
817    #[inline]
818    fn dictionary_changed(&mut self, object: ObjectId) {
819        if self.objects.get(object.0).kind == ObjectKind::GlobalObject {
820            self.bump_global_generation();
821        }
822    }
823
824    /// Moves `object` to an unshared dictionary shape. `keep` are extra values
825    /// to root across the allocation.
826    pub(crate) fn enter_dictionary(
827        &mut self,
828        object: ObjectId,
829        keep: &[Value],
830    ) -> Result<(), HeapError> {
831        debug_assert!(!self.is_pending_closure(object), "a pending closure");
832        let mut roots = vec![Value::object(object)];
833        roots.extend_from_slice(keep);
834        self.safepoint(
835            &Need {
836                shapes: 1,
837                ..Need::default()
838            },
839            &roots,
840        )?;
841        let header = *self.objects.get(object.0);
842        let shape = self.shapes.get(header.shape.0);
843        let (prototype, kind) = (shape.prototype, shape.kind);
844        let dictionary = Dictionary::from_entries(self.shape_entries(header.shape));
845        let shape = self.dictionary_shape(prototype, kind, dictionary);
846        let header = self.objects.get_mut(object.0);
847        header.shape = shape;
848        header.flags |= flags::DICTIONARY;
849        Ok(())
850    }
851
852    /// Moves the own properties `front` names to the start of a dictionary
853    /// object's key order, in that order (realm setup: the global object's
854    /// order is QuickJS's, not the install order). Values keep their slots.
855    pub fn reorder_dictionary(&mut self, object: ObjectId, front: &[PropertyKey]) {
856        let header = *self.objects.get(object.0);
857        debug_assert!(header.has(flags::DICTIONARY), "a dictionary object");
858        if let Some(dictionary) = self
859            .shapes
860            .get_mut(header.shape.0)
861            .dictionary
862            .as_deref_mut()
863        {
864            dictionary.reorder(front);
865            self.dictionary_changed(object);
866        }
867    }
868
869    /// Deletes an own property; returns whether it existed. The object enters
870    /// dictionary mode.
871    pub fn delete_property(
872        &mut self,
873        object: ObjectId,
874        key: PropertyKey,
875    ) -> Result<bool, HeapError> {
876        if self.lookup_own(object, key).is_none() {
877            return Ok(false);
878        }
879        if !self.is_dictionary(object) {
880            self.enter_dictionary(object, &[])?;
881        }
882        let header = *self.objects.get(object.0);
883        let dictionary = self
884            .shapes
885            .get_mut(header.shape.0)
886            .dictionary
887            .as_deref_mut()
888            .expect("dictionary shape");
889        let (_, flags) = dictionary.find(key).expect("present key");
890        let slot = dictionary.remove(key).expect("present key");
891        for slot in slot..slot + flags.width() {
892            self.values.set(header.slots(), slot, Value::EMPTY);
893        }
894        self.dictionary_changed(object);
895        if header.has(flags::USED_AS_PROTOTYPE) {
896            self.invalidate(object);
897        }
898        Ok(true)
899    }
900
901    /// Changes the flags of an own property, keeping its slot. Returns whether
902    /// the property exists.
903    pub fn set_property_flags(
904        &mut self,
905        object: ObjectId,
906        key: PropertyKey,
907        flags: PropertyFlags,
908    ) -> Result<bool, HeapError> {
909        let Some((_, old)) = self.lookup_own(object, key) else {
910            return Ok(false);
911        };
912        if old == flags {
913            return Ok(true);
914        }
915        debug_assert!(!self.is_pending_closure(object), "a pending closure");
916        debug_assert_eq!(old.width(), flags.width(), "a data/accessor change");
917        let header = *self.objects.get(object.0);
918        if header.has(flags::DICTIONARY) {
919            let dictionary = self
920                .shapes
921                .get_mut(header.shape.0)
922                .dictionary
923                .as_deref_mut()
924                .expect("dictionary shape");
925            dictionary.set_flags(key, flags);
926            self.dictionary_changed(object);
927        } else {
928            let mut entries = self.shape_entries(header.shape);
929            for entry in &mut entries {
930                if entry.0 == key {
931                    entry.1 = flags;
932                }
933            }
934            let prototype = self.prototype_of(object);
935            self.replay_shape(object, prototype, &entries)?;
936        }
937        if header.has(flags::USED_AS_PROTOTYPE) {
938            self.invalidate(object);
939        }
940        Ok(true)
941    }
942
943    /// Replaces an own property's flags and values, keeping its place in the
944    /// key order. A change between data and accessor moves the object to
945    /// dictionary mode. `values` are rooted for the call.
946    pub fn reshape_property(
947        &mut self,
948        object: ObjectId,
949        key: PropertyKey,
950        flags: PropertyFlags,
951        values: [Value; 2],
952    ) -> Result<(), HeapError> {
953        let (_, old) = self.lookup_own(object, key).expect("an own property");
954        let mark = self.roots.stack.mark();
955        self.roots.stack.push(values[0]);
956        self.roots.stack.push(values[1]);
957        let result = (|| {
958            if old.width() == flags.width() {
959                self.set_property_flags(object, key, flags)?;
960            } else {
961                if !self.is_dictionary(object) {
962                    self.enter_dictionary(object, &[])?;
963                }
964                let header = *self.objects.get(object.0);
965                let dictionary = self
966                    .shapes
967                    .get_mut(header.shape.0)
968                    .dictionary
969                    .as_deref_mut()
970                    .expect("dictionary shape");
971                let slot = dictionary.reslot(key, flags).expect("present key");
972                let end = slot + flags.width();
973                let slots = header.slots();
974                if end > slots.len {
975                    let new_len = (slots.len * 2).max(end).max(4);
976                    self.safepoint(
977                        &Need {
978                            values: [new_len, 0],
979                            ..Need::default()
980                        },
981                        &[Value::object(object)],
982                    )?;
983                    let slots = self.values.grow_slots(slots, new_len, &mut self.gc.room);
984                    self.objects.get_mut(object.0).set_slots(slots);
985                }
986                self.dictionary_changed(object);
987                if header.has(flags::USED_AS_PROTOTYPE) {
988                    self.invalidate(object);
989                }
990            }
991            let (slot, _) = self.lookup_own(object, key).expect("an own property");
992            self.set_slot(object, slot, values[0]);
993            if flags.width() == 2 {
994                self.set_slot(object, slot + 1, values[1]);
995            }
996            Ok(())
997        })();
998        self.roots.stack.truncate(mark);
999        result
1000    }
1001
1002    /// Gives a transition-tree object the shape reached from the root for
1003    /// `(prototype, kind)` by adding `entries` in order. Slots keep their
1004    /// positions.
1005    fn replay_shape(
1006        &mut self,
1007        object: ObjectId,
1008        prototype: Option<ObjectId>,
1009        entries: &[(PropertyKey, PropertyFlags, u32)],
1010    ) -> Result<(), HeapError> {
1011        let need = Need {
1012            shapes: entries.len() + 1,
1013            ..Need::default()
1014        };
1015        let proto_value = prototype.map_or(Value::UNDEFINED, Value::object);
1016        self.safepoint(&need, &[Value::object(object), proto_value])?;
1017        let kind = self.shapes.get(self.objects.get(object.0).shape.0).kind;
1018        let mut shape = self.root_shape(prototype, kind);
1019        for &(key, flags, slot) in entries {
1020            debug_assert_eq!(slot, self.shapes.get(shape.0).width);
1021            shape = self.transition(shape, key, flags);
1022        }
1023        self.objects.get_mut(object.0).shape = shape;
1024        Ok(())
1025    }
1026
1027    /// Sets `[[Prototype]]`. `PrototypeCycle` if `prototype` inherits from
1028    /// `object`.
1029    pub fn set_prototype(
1030        &mut self,
1031        object: ObjectId,
1032        prototype: Option<ObjectId>,
1033    ) -> Result<(), HeapError> {
1034        let old = self.prototype_of(object);
1035        if old == prototype {
1036            return Ok(());
1037        }
1038        let mut walk = prototype;
1039        while let Some(current) = walk {
1040            if current == object {
1041                return Err(HeapError::PrototypeCycle);
1042            }
1043            walk = self.prototype_of(current);
1044        }
1045        let header = *self.objects.get(object.0);
1046        if header.has(flags::DICTIONARY) {
1047            if let Some(prototype) = prototype {
1048                self.ensure_prototype(prototype);
1049            }
1050            let shape = self.shapes.get_mut(header.shape.0);
1051            shape.prototype = prototype;
1052            shape.validity = ValidityCellId(NONE);
1053        } else {
1054            let entries = self.shape_entries(header.shape);
1055            self.replay_shape(object, prototype, &entries)?;
1056        }
1057        if header.has(flags::USED_AS_PROTOTYPE) {
1058            if let Some(old) = old
1059                && let Some(info) = self.prototypes.get_mut(&old)
1060            {
1061                info.dependents.retain(|&dependent| dependent != object);
1062            }
1063            if let Some(new) = prototype {
1064                self.prototypes
1065                    .entry(new)
1066                    .or_default()
1067                    .dependents
1068                    .push(object);
1069            }
1070            self.invalidate(object);
1071        }
1072        Ok(())
1073    }
1074
1075    /// Own keys in `[[OwnPropertyKeys]]` order: array indices ascending
1076    /// (elements and named), then strings, then symbols, each in insertion
1077    /// order. Private names are hidden.
1078    pub fn own_keys(&self, object: ObjectId) -> Vec<PropertyKey> {
1079        let header = self.objects.get(object.0);
1080        let mut indices = Vec::new();
1081        let elements = header.elements();
1082        let element_count = if header.kind == ObjectKind::Array {
1083            header.payload.min(elements.len)
1084        } else {
1085            elements.len
1086        };
1087        for index in 0..element_count {
1088            if !self.values.get(elements, index).is_empty()
1089                && let Some(key) = PropertyKey::index(index)
1090            {
1091                indices.push((index, key));
1092            }
1093        }
1094        let mut strings = Vec::new();
1095        let mut symbols = Vec::new();
1096        for (key, _, _) in self.shape_entries(header.shape) {
1097            match key.kind() {
1098                KeyKind::Index(index) => indices.push((index, key)),
1099                KeyKind::String(id) => match self.strings.get(id.0).array_index {
1100                    NONE => strings.push(key),
1101                    index => indices.push((index, key)),
1102                },
1103                KeyKind::Symbol(id) => {
1104                    if !self.symbols.get(id.0).private {
1105                        symbols.push(key);
1106                    }
1107                }
1108            }
1109        }
1110        indices.sort_by_key(|&(index, _)| index);
1111        indices
1112            .into_iter()
1113            .map(|(_, key)| key)
1114            .chain(strings)
1115            .chain(symbols)
1116            .collect()
1117    }
1118
1119    /// Whether `object` has an element or a named property with an index
1120    /// key.
1121    fn has_index_property(&self, object: ObjectId) -> bool {
1122        let header = self.objects.get(object.0);
1123        let elements = header.elements();
1124        if self
1125            .values
1126            .slice(elements)
1127            .iter()
1128            .any(|value| !value.is_empty())
1129        {
1130            return true;
1131        }
1132        self.shape_entries(header.shape)
1133            .iter()
1134            .any(|(key, _, _)| match key.kind() {
1135                KeyKind::Index(_) => true,
1136                KeyKind::String(id) => self.strings.get(id.0).array_index != NONE,
1137                KeyKind::Symbol(_) => false,
1138            })
1139    }
1140
1141    // ---- prototypes and validity cells ----
1142
1143    /// Flags `prototype` as used as a prototype and registers it with its own
1144    /// prototype's dependents, up the chain.
1145    pub(crate) fn ensure_prototype(&mut self, prototype: ObjectId) {
1146        let header = self.objects.get_mut(prototype.0);
1147        if header.has(flags::USED_AS_PROTOTYPE) {
1148            return;
1149        }
1150        header.flags |= flags::USED_AS_PROTOTYPE;
1151        let kind = header.kind;
1152        if self.holes_writable
1153            && (kind.is_exotic()
1154                || matches!(kind, ObjectKind::Arguments)
1155                || self.has_index_property(prototype))
1156        {
1157            self.holes_writable = false;
1158        }
1159        self.prototypes.entry(prototype).or_default();
1160        if let Some(parent) = self.prototype_of(prototype) {
1161            self.ensure_prototype(parent);
1162            self.prototypes
1163                .entry(parent)
1164                .or_default()
1165                .dependents
1166                .push(prototype);
1167        }
1168    }
1169
1170    /// Clears the validity cell of `object` and of every prototype object that
1171    /// inherits from it.
1172    pub(crate) fn invalidate(&mut self, object: ObjectId) {
1173        let mut work = vec![object];
1174        while let Some(current) = work.pop() {
1175            if let Some(info) = self.prototypes.get_mut(&current) {
1176                if let Some(cell) = info.cell.take() {
1177                    self.cells.get_mut(cell.0).valid = false;
1178                }
1179                work.extend_from_slice(&info.dependents);
1180            }
1181        }
1182    }
1183
1184    /// The cell guarding `object`'s prototype chain: valid until any object on
1185    /// that chain changes. Allocates a fresh cell after an invalidation.
1186    pub fn validity_cell(&mut self, object: ObjectId) -> Result<ValidityCellId, HeapError> {
1187        let shape_id = self.objects.get(object.0).shape;
1188        let shape = self.shapes.get(shape_id.0);
1189        let Some(prototype) = shape.prototype else {
1190            return Ok(STATIC_CELL);
1191        };
1192        if shape.validity.0 != NONE && self.cells.get(shape.validity.0).valid {
1193            return Ok(shape.validity);
1194        }
1195        let cell = self.chain_cell_keeping(prototype, Value::object(object))?;
1196        // The object was rooted, so its shape is unchanged.
1197        self.shapes.get_mut(shape_id.0).validity = cell;
1198        Ok(cell)
1199    }
1200
1201    /// The cell guarding the chain that starts at `prototype`: valid until
1202    /// `prototype` or any object above it changes. It is the cell
1203    /// [`Heap::validity_cell`] gives an object whose prototype is `prototype`;
1204    /// the string-receiver cache uses it for `%String.prototype%`, which no
1205    /// object inherits from.
1206    pub fn chain_cell(&mut self, prototype: ObjectId) -> Result<ValidityCellId, HeapError> {
1207        self.chain_cell_keeping(prototype, Value::UNDEFINED)
1208    }
1209
1210    fn chain_cell_keeping(
1211        &mut self,
1212        prototype: ObjectId,
1213        keep: Value,
1214    ) -> Result<ValidityCellId, HeapError> {
1215        self.ensure_prototype(prototype);
1216        if let Some(cell) = self.prototypes.get(&prototype).and_then(|info| info.cell) {
1217            return Ok(cell);
1218        }
1219        self.safepoint(
1220            &Need {
1221                cells: 1,
1222                ..Need::default()
1223            },
1224            &[Value::object(prototype), keep],
1225        )?;
1226        let cell = ValidityCellId(
1227            self.cells
1228                .alloc(ValidityCell { valid: true }, &mut self.gc.room),
1229        );
1230        self.prototypes.entry(prototype).or_default().cell = Some(cell);
1231        Ok(cell)
1232    }
1233
1234    #[inline]
1235    pub fn cell_is_valid(&self, cell: ValidityCellId) -> bool {
1236        self.cells.get(cell.0).valid
1237    }
1238}