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