1use 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
30pub const TABLE_THRESHOLD: u32 = 8;
32pub const DICTIONARY_THRESHOLD: u32 = 64;
34pub 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 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 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#[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 pub slot: u32,
163}
164
165#[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 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 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 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 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 pub parent: Option<ShapeId>,
298 pub key: PropertyKey,
300 pub flags: PropertyFlags,
301 pub slot: u32,
302 pub count: u32,
304 pub width: u32,
307 pub prototype: Option<ObjectId>,
308 pub kind: ObjectKind,
309 pub transitions: Transitions,
310 pub table: OnceCell<Box<FxHashMap<PropertyKey, (u32, PropertyFlags)>>>,
313 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 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#[derive(Default, Debug)]
366pub struct PrototypeInfo {
367 pub cell: Option<ValidityCellId>,
368 pub dependents: Vec<ObjectId>,
370}
371
372impl Heap {
373 #[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 #[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 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 #[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 #[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 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 #[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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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(¤t) {
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 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 self.shapes.get_mut(shape_id.0).validity = cell;
1127 Ok(cell)
1128 }
1129
1130 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}