1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "vm/PropMap-inl.h"
9 #include "gc/HashUtil.h"
10 #include "js/GCVector.h"
11 #include "vm/JSObject.h"
13 #include "gc/GCContext-inl.h"
14 #include "gc/Marking-inl.h"
15 #include "vm/JSContext-inl.h"
16 #include "vm/ObjectFlags-inl.h"
20 void PropMap::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
,
21 size_t* children
, size_t* tables
) const {
22 if (isShared() && asShared()->hasChildrenSet()) {
23 auto* set
= asShared()->treeDataRef().children
.toChildrenSet();
24 *children
+= set
->shallowSizeOfIncludingThis(mallocSizeOf
);
26 if (canHaveTable() && asLinked()->hasTable()) {
27 *tables
+= asLinked()->data_
.table
->sizeOfIncludingThis(mallocSizeOf
);
32 SharedPropMap
* SharedPropMap::create(JSContext
* cx
, Handle
<SharedPropMap
*> prev
,
33 HandleId id
, PropertyInfo prop
) {
34 // If the first property has a slot number <= MaxSlotNumber, all properties
35 // added later will have a slot number <= CompactPropertyInfo::MaxSlotNumber
36 // so we can use a CompactPropMap.
37 static constexpr size_t MaxFirstSlot
=
38 CompactPropertyInfo::MaxSlotNumber
- (PropMap::Capacity
- 1);
40 if (!prev
&& prop
.maybeSlot() <= MaxFirstSlot
) {
41 return cx
->newCell
<CompactPropMap
>(id
, prop
);
44 return cx
->newCell
<NormalPropMap
>(prev
, id
, prop
);
48 SharedPropMap
* SharedPropMap::createInitial(JSContext
* cx
, HandleId id
,
50 // Lookup or create a shared map based on the first property.
52 using Lookup
= InitialPropMapHasher::Lookup
;
54 auto& table
= cx
->zone()->shapeZone().initialPropMaps
;
56 auto p
= MakeDependentAddPtr(cx
, table
, Lookup(id
, prop
));
61 SharedPropMap
* result
= create(cx
, /* prev = */ nullptr, id
, prop
);
66 Lookup
lookup(id
, prop
);
67 if (!p
.add(cx
, table
, lookup
, result
)) {
75 SharedPropMap
* SharedPropMap::clone(JSContext
* cx
, Handle
<SharedPropMap
*> map
,
77 MOZ_ASSERT(length
> 0);
79 if (map
->isCompact()) {
80 Rooted
<CompactPropMap
*> prev(cx
, map
->asCompact());
81 return cx
->newCell
<CompactPropMap
>(prev
, length
);
84 Rooted
<NormalPropMap
*> prev(cx
, map
->asNormal());
85 return cx
->newCell
<NormalPropMap
>(prev
, length
);
89 DictionaryPropMap
* SharedPropMap::toDictionaryMap(JSContext
* cx
,
90 Handle
<SharedPropMap
*> map
,
92 // Starting at the last map, clone each shared map to a new dictionary map.
94 Rooted
<DictionaryPropMap
*> lastDictMap(cx
);
95 Rooted
<DictionaryPropMap
*> nextDictMap(cx
);
97 Rooted
<SharedPropMap
*> sharedMap(cx
, map
);
98 uint32_t sharedLength
= length
;
100 sharedMap
->setHadDictionaryConversion();
102 DictionaryPropMap
* dictMap
;
103 if (sharedMap
->isCompact()) {
104 Rooted
<CompactPropMap
*> prev(cx
, sharedMap
->asCompact());
105 dictMap
= cx
->newCell
<DictionaryPropMap
>(prev
, sharedLength
);
107 Rooted
<NormalPropMap
*> prev(cx
, sharedMap
->asNormal());
108 dictMap
= cx
->newCell
<DictionaryPropMap
>(prev
, sharedLength
);
115 lastDictMap
= dictMap
;
119 nextDictMap
->initPrevious(dictMap
);
121 nextDictMap
= dictMap
;
123 if (!sharedMap
->hasPrevious()) {
126 sharedMap
= sharedMap
->asNormal()->previous();
127 sharedLength
= PropMap::Capacity
;
133 static MOZ_ALWAYS_INLINE SharedPropMap
* PropMapChildReadBarrier(
134 SharedPropMap
* parent
, SharedPropMap
* child
) {
135 JS::Zone
* zone
= child
->zone();
136 if (zone
->needsIncrementalBarrier()) {
137 // We need a read barrier for the map tree, since these are weak
143 if (MOZ_UNLIKELY(zone
->isGCSweeping() &&
144 IsAboutToBeFinalizedUnbarriered(child
))) {
145 // The map we've found is unreachable and due to be finalized, so
146 // remove our weak reference to it and don't use it.
147 MOZ_ASSERT(parent
->isMarkedAny());
148 parent
->removeChild(zone
->runtimeFromMainThread()->gcContext(), child
);
152 // We don't yield to the mutator when the zone is in this state so we don't
153 // need to account for it here.
154 MOZ_ASSERT(!zone
->isGCCompacting());
159 SharedPropMap
* SharedPropMap::lookupChild(uint32_t length
, HandleId id
,
161 MOZ_ASSERT(length
> 0);
163 SharedChildrenPtr children
= treeDataRef().children
;
164 if (children
.isNone()) {
168 if (!hasChildrenSet()) {
169 SharedPropMapAndIndex prevChild
= children
.toSingleChild();
170 if (prevChild
.index() == length
- 1) {
171 SharedPropMap
* child
= prevChild
.map();
172 uint32_t newPropIndex
= indexOfNextProperty(length
- 1);
173 if (child
->matchProperty(newPropIndex
, id
, prop
)) {
174 return PropMapChildReadBarrier(this, child
);
180 SharedChildrenSet
* set
= children
.toChildrenSet();
181 SharedChildrenHasher::Lookup
lookup(id
, prop
, length
- 1);
182 if (auto p
= set
->lookup(lookup
)) {
183 MOZ_ASSERT(p
->index() == length
- 1);
184 SharedPropMap
* child
= p
->map();
185 return PropMapChildReadBarrier(this, child
);
190 bool SharedPropMap::addChild(JSContext
* cx
, SharedPropMapAndIndex child
,
191 HandleId id
, PropertyInfo prop
) {
192 SharedPropMap
* childMap
= child
.map();
195 // If the parent map was full, the child map must have the parent as
196 // |previous| map. Else, the parent and child have the same |previous| map.
197 if (childMap
->hasPrevious()) {
198 if (child
.index() == PropMap::Capacity
- 1) {
199 MOZ_ASSERT(childMap
->asLinked()->previous() == this);
201 MOZ_ASSERT(childMap
->asLinked()->previous() == asLinked()->previous());
204 MOZ_ASSERT(!hasPrevious());
208 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
210 if (childrenRef
.isNone()) {
211 childrenRef
.setSingleChild(child
);
212 childMap
->treeDataRef().setParent(this, child
.index());
216 SharedChildrenHasher::Lookup
lookup(id
, prop
, child
.index());
218 if (hasChildrenSet()) {
219 if (!childrenRef
.toChildrenSet()->putNew(lookup
, child
)) {
220 ReportOutOfMemory(cx
);
224 auto hash
= MakeUnique
<SharedChildrenSet
>();
225 if (!hash
|| !hash
->reserve(2)) {
226 ReportOutOfMemory(cx
);
229 SharedPropMapAndIndex firstChild
= childrenRef
.toSingleChild();
230 SharedPropMap
* firstChildMap
= firstChild
.map();
231 uint32_t firstChildIndex
= indexOfNextProperty(firstChild
.index());
232 SharedChildrenHasher::Lookup
lookupFirst(
233 firstChildMap
->getPropertyInfoWithKey(firstChildIndex
),
235 hash
->putNewInfallible(lookupFirst
, firstChild
);
236 hash
->putNewInfallible(lookup
, child
);
238 childrenRef
.setChildrenSet(hash
.release());
240 AddCellMemory(this, sizeof(SharedChildrenSet
), MemoryUse::PropMapChildren
);
243 childMap
->treeDataRef().setParent(this, child
.index());
248 bool SharedPropMap::addProperty(JSContext
* cx
, const JSClass
* clasp
,
249 MutableHandle
<SharedPropMap
*> map
,
250 uint32_t* mapLength
, HandleId id
,
251 PropertyFlags flags
, ObjectFlags
* objectFlags
,
253 MOZ_ASSERT(!flags
.isCustomDataProperty());
255 *slot
= SharedPropMap::slotSpan(clasp
, map
, *mapLength
);
257 if (MOZ_UNLIKELY(*slot
> SHAPE_MAXIMUM_SLOT
)) {
258 ReportAllocationOverflow(cx
);
263 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
265 PropertyInfo prop
= PropertyInfo(flags
, *slot
);
266 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
270 bool SharedPropMap::addPropertyInReservedSlot(
271 JSContext
* cx
, const JSClass
* clasp
, MutableHandle
<SharedPropMap
*> map
,
272 uint32_t* mapLength
, HandleId id
, PropertyFlags flags
, uint32_t slot
,
273 ObjectFlags
* objectFlags
) {
274 MOZ_ASSERT(!flags
.isCustomDataProperty());
276 MOZ_ASSERT(slot
< JSCLASS_RESERVED_SLOTS(clasp
));
277 MOZ_ASSERT_IF(map
, map
->lastUsedSlot(*mapLength
) < slot
);
280 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
282 PropertyInfo prop
= PropertyInfo(flags
, slot
);
283 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
287 bool SharedPropMap::addPropertyWithKnownSlot(JSContext
* cx
,
288 const JSClass
* clasp
,
289 MutableHandle
<SharedPropMap
*> map
,
290 uint32_t* mapLength
, HandleId id
,
291 PropertyFlags flags
, uint32_t slot
,
292 ObjectFlags
* objectFlags
) {
293 MOZ_ASSERT(!flags
.isCustomDataProperty());
295 if (MOZ_UNLIKELY(slot
< JSCLASS_RESERVED_SLOTS(clasp
))) {
296 return addPropertyInReservedSlot(cx
, clasp
, map
, mapLength
, id
, flags
, slot
,
300 MOZ_ASSERT(slot
== SharedPropMap::slotSpan(clasp
, map
, *mapLength
));
301 MOZ_RELEASE_ASSERT(slot
<= SHAPE_MAXIMUM_SLOT
);
304 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
306 PropertyInfo prop
= PropertyInfo(flags
, slot
);
307 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
311 bool SharedPropMap::addCustomDataProperty(JSContext
* cx
, const JSClass
* clasp
,
312 MutableHandle
<SharedPropMap
*> map
,
313 uint32_t* mapLength
, HandleId id
,
315 ObjectFlags
* objectFlags
) {
316 MOZ_ASSERT(flags
.isCustomDataProperty());
318 // Custom data properties don't have a slot. Copy the last property's slot
319 // number to simplify the implementation of SharedPropMap::slotSpan.
320 uint32_t slot
= map
? map
->lastUsedSlot(*mapLength
) : SHAPE_INVALID_SLOT
;
323 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
325 PropertyInfo prop
= PropertyInfo(flags
, slot
);
326 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
330 bool SharedPropMap::addPropertyInternal(JSContext
* cx
,
331 MutableHandle
<SharedPropMap
*> map
,
332 uint32_t* mapLength
, HandleId id
,
335 // Adding the first property.
336 MOZ_ASSERT(*mapLength
== 0);
337 map
.set(SharedPropMap::createInitial(cx
, id
, prop
));
345 MOZ_ASSERT(*mapLength
> 0);
347 if (*mapLength
< PropMap::Capacity
) {
348 // Use the next map entry if possible.
349 if (!map
->hasKey(*mapLength
)) {
350 if (map
->canHaveTable()) {
351 JS::AutoCheckCannotGC nogc
;
352 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
353 if (!table
->add(cx
, id
, PropMapAndIndex(map
, *mapLength
))) {
358 map
->initProperty(*mapLength
, id
, prop
);
362 if (map
->matchProperty(*mapLength
, id
, prop
)) {
367 // The next entry can't be used so look up or create a child map. The child
368 // map is a clone of this map up to mapLength, with the new property stored
369 // as the next entry.
371 if (SharedPropMap
* child
= map
->lookupChild(*mapLength
, id
, prop
)) {
377 SharedPropMap
* child
= SharedPropMap::clone(cx
, map
, *mapLength
);
381 child
->initProperty(*mapLength
, id
, prop
);
383 SharedPropMapAndIndex
childEntry(child
, *mapLength
- 1);
384 if (!map
->addChild(cx
, childEntry
, id
, prop
)) {
393 // This map is full so look up or create a child map.
394 MOZ_ASSERT(*mapLength
== PropMap::Capacity
);
396 if (SharedPropMap
* child
= map
->lookupChild(*mapLength
, id
, prop
)) {
402 SharedPropMap
* child
= SharedPropMap::create(cx
, map
, id
, prop
);
407 SharedPropMapAndIndex
childEntry(child
, PropMap::Capacity
- 1);
408 if (!map
->addChild(cx
, childEntry
, id
, prop
)) {
412 // As an optimization, pass the table to the new child map, unless adding the
413 // property to it OOMs. Measurements indicate this gets rid of a large number
414 // of PropMapTable allocations because we don't need to create a second table
415 // when the parent map won't be used again as last map.
416 if (map
->canHaveTable()) {
417 JS::AutoCheckCannotGC nogc
;
418 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
419 // Trigger a pre-barrier on the parent map to appease the pre-barrier
420 // verifier, because edges from the table are disappearing (even though
421 // these edges are strictly redundant with the |previous| maps).
422 gc::PreWriteBarrier(map
.get());
423 if (table
->add(cx
, id
, PropMapAndIndex(child
, 0))) {
424 map
->asLinked()->handOffTableTo(child
->asLinked());
426 cx
->recoverFromOutOfMemory();
436 static PropertyFlags
ComputeFlagsForSealOrFreeze(PropertyKey key
,
438 IntegrityLevel level
) {
439 // Private fields are not visible to SetIntegrityLevel.
440 if (key
.isSymbol() && key
.toSymbol()->isPrivateName()) {
444 // Make all properties non-configurable; if freezing, make data properties
446 flags
.clearFlag(PropertyFlag::Configurable
);
447 if (level
== IntegrityLevel::Frozen
&& flags
.isDataDescriptor()) {
448 flags
.clearFlag(PropertyFlag::Writable
);
455 bool SharedPropMap::freezeOrSealProperties(JSContext
* cx
, IntegrityLevel level
,
456 const JSClass
* clasp
,
457 MutableHandle
<SharedPropMap
*> map
,
459 ObjectFlags
* objectFlags
) {
460 // Add all maps to a Vector so we can iterate over them in reverse order
461 // (property definition order).
462 JS::RootedVector
<SharedPropMap
*> maps(cx
);
464 SharedPropMap
* curMap
= map
;
466 if (!maps
.append(curMap
)) {
469 if (!curMap
->hasPrevious()) {
472 curMap
= curMap
->asNormal()->previous();
476 // Build a new SharedPropMap by adding each property with the changed
478 Rooted
<SharedPropMap
*> newMap(cx
);
479 uint32_t newMapLength
= 0;
481 Rooted
<PropertyKey
> key(cx
);
482 Rooted
<SharedPropMap
*> curMap(cx
);
484 for (size_t i
= maps
.length(); i
> 0; i
--) {
485 curMap
= maps
[i
- 1];
486 uint32_t len
= (i
== 1) ? mapLength
: PropMap::Capacity
;
488 for (uint32_t j
= 0; j
< len
; j
++) {
489 key
= curMap
->getKey(j
);
490 PropertyInfo prop
= curMap
->getPropertyInfo(j
);
491 PropertyFlags flags
=
492 ComputeFlagsForSealOrFreeze(key
, prop
.flags(), level
);
494 if (prop
.isCustomDataProperty()) {
495 if (!addCustomDataProperty(cx
, clasp
, &newMap
, &newMapLength
, key
,
496 flags
, objectFlags
)) {
500 if (!addPropertyWithKnownSlot(cx
, clasp
, &newMap
, &newMapLength
, key
,
501 flags
, prop
.slot(), objectFlags
)) {
509 MOZ_ASSERT(newMapLength
== mapLength
);
513 void LinkedPropMap::handOffTableTo(LinkedPropMap
* next
) {
514 MOZ_ASSERT(hasTable());
515 MOZ_ASSERT(!next
->hasTable());
517 next
->data_
.table
= data_
.table
;
518 data_
.table
= nullptr;
520 // Note: for tables currently only sizeof(PropMapTable) is tracked.
521 RemoveCellMemory(this, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
522 AddCellMemory(next
, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
525 void DictionaryPropMap::handOffLastMapStateTo(DictionaryPropMap
* newLast
) {
526 // A dictionary object's last map contains the table, slot freeList, and
527 // holeCount. These fields always have their initial values for non-last maps.
529 MOZ_ASSERT(this != newLast
);
531 if (asLinked()->hasTable()) {
532 asLinked()->handOffTableTo(newLast
->asLinked());
535 MOZ_ASSERT(newLast
->freeList_
== SHAPE_INVALID_SLOT
);
536 newLast
->freeList_
= freeList_
;
537 freeList_
= SHAPE_INVALID_SLOT
;
539 MOZ_ASSERT(newLast
->holeCount_
== 0);
540 newLast
->holeCount_
= holeCount_
;
545 bool DictionaryPropMap::addProperty(JSContext
* cx
, const JSClass
* clasp
,
546 MutableHandle
<DictionaryPropMap
*> map
,
547 uint32_t* mapLength
, HandleId id
,
548 PropertyFlags flags
, uint32_t slot
,
549 ObjectFlags
* objectFlags
) {
553 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
554 PropertyInfo prop
= PropertyInfo(flags
, slot
);
556 if (*mapLength
< PropMap::Capacity
) {
557 JS::AutoCheckCannotGC nogc
;
558 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
559 if (!table
->add(cx
, id
, PropMapAndIndex(map
, *mapLength
))) {
563 map
->initProperty(*mapLength
, id
, prop
);
568 DictionaryPropMap
* newMap
= cx
->newCell
<DictionaryPropMap
>(map
, id
, prop
);
573 JS::AutoCheckCannotGC nogc
;
574 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
575 if (!table
->add(cx
, id
, PropMapAndIndex(newMap
, 0))) {
580 MOZ_ASSERT(newMap
->previous() == map
);
581 map
->handOffLastMapStateTo(newMap
);
588 void DictionaryPropMap::changeProperty(JSContext
* cx
, const JSClass
* clasp
,
589 uint32_t index
, PropertyFlags flags
,
591 ObjectFlags
* objectFlags
) {
592 MOZ_ASSERT(hasKey(index
));
593 *objectFlags
= GetObjectFlagsForNewProperty(clasp
, *objectFlags
,
594 getKey(index
), flags
, cx
);
595 linkedData_
.propInfos
[index
] = PropertyInfo(flags
, slot
);
598 void DictionaryPropMap::freezeOrSealProperties(JSContext
* cx
,
599 IntegrityLevel level
,
600 const JSClass
* clasp
,
602 ObjectFlags
* objectFlags
) {
603 DictionaryPropMap
* curMap
= this;
605 for (uint32_t i
= 0; i
< mapLength
; i
++) {
606 if (!curMap
->hasKey(i
)) {
609 PropertyKey key
= curMap
->getKey(i
);
610 PropertyFlags flags
= curMap
->getPropertyInfo(i
).flags();
611 flags
= ComputeFlagsForSealOrFreeze(key
, flags
, level
);
612 curMap
->changePropertyFlags(cx
, clasp
, i
, flags
, objectFlags
);
614 curMap
= curMap
->previous();
615 mapLength
= PropMap::Capacity
;
620 void DictionaryPropMap::skipTrailingHoles(MutableHandle
<DictionaryPropMap
*> map
,
621 uint32_t* mapLength
) {
622 // After removing a property, rewind map/mapLength so that the last property
623 // is not a hole. This ensures accessing the last property of a map can always
624 // be done without checking for holes.
627 MOZ_ASSERT(*mapLength
> 0);
629 if (map
->hasKey(*mapLength
- 1)) {
634 } while (*mapLength
> 0);
636 if (!map
->previous()) {
637 // The dictionary map is empty, return the initial map with mapLength 0.
638 MOZ_ASSERT(*mapLength
== 0);
639 MOZ_ASSERT(map
->holeCount_
== 0);
643 map
->handOffLastMapStateTo(map
->previous());
644 map
.set(map
->previous());
645 *mapLength
= PropMap::Capacity
;
650 void DictionaryPropMap::removeProperty(JSContext
* cx
,
651 MutableHandle
<DictionaryPropMap
*> map
,
652 uint32_t* mapLength
, PropMapTable
* table
,
653 PropMapTable::Ptr
& ptr
) {
655 MOZ_ASSERT(*mapLength
> 0);
657 JS::AutoCheckCannotGC nogc
;
658 MOZ_ASSERT(map
->asLinked()->maybeTable(nogc
) == table
);
660 bool removingLast
= (map
== ptr
->map() && *mapLength
- 1 == ptr
->index());
661 ptr
->map()->asDictionary()->clearProperty(ptr
->index());
666 skipTrailingHoles(map
, mapLength
);
668 maybeCompact(cx
, map
, mapLength
);
672 void DictionaryPropMap::densifyElements(JSContext
* cx
,
673 MutableHandle
<DictionaryPropMap
*> map
,
677 MOZ_ASSERT(*mapLength
> 0);
679 JS::AutoCheckCannotGC nogc
;
680 PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
);
682 DictionaryPropMap
* currentMap
= map
;
683 uint32_t currentLen
= *mapLength
;
685 for (uint32_t i
= 0; i
< currentLen
; i
++) {
686 PropertyKey key
= currentMap
->getKey(i
);
688 if (!IdIsIndex(key
, &index
)) {
692 // The caller must have checked all sparse elements are plain data
694 PropertyInfo prop
= currentMap
->getPropertyInfo(i
);
695 MOZ_ASSERT(prop
.flags() == PropertyFlags::defaultDataPropFlags
);
697 uint32_t slot
= prop
.slot();
698 Value value
= obj
->getSlot(slot
);
699 obj
->setDenseElement(index
, value
);
700 obj
->freeDictionarySlot(slot
);
703 PropMapTable::Ptr p
= table
->lookupRaw(key
);
708 currentMap
->clearProperty(i
);
711 currentMap
= currentMap
->previous();
712 currentLen
= PropMap::Capacity
;
713 } while (currentMap
);
715 skipTrailingHoles(map
, mapLength
);
716 maybeCompact(cx
, map
, mapLength
);
719 void DictionaryPropMap::maybeCompact(JSContext
* cx
,
720 MutableHandle
<DictionaryPropMap
*> map
,
721 uint32_t* mapLength
) {
722 // If there are no holes, there's nothing to compact.
723 if (map
->holeCount_
== 0) {
727 JS::AutoCheckCannotGC nogc
;
728 PropMapTable
* table
= map
->asLinked()->ensureTable(cx
, nogc
);
730 // Compacting is optional so just return.
731 cx
->recoverFromOutOfMemory();
735 // Heuristic: only compact if the number of holes >= the number of (non-hole)
737 if (map
->holeCount_
< table
->entryCount()) {
741 // Add all dictionary maps to a Vector so that we can iterate over them in
742 // reverse order (property definition order). If appending to the Vector OOMs,
743 // just return because compacting is optional.
744 Vector
<DictionaryPropMap
*, 32, SystemAllocPolicy
> maps
;
745 for (DictionaryPropMap
* curMap
= map
; curMap
; curMap
= curMap
->previous()) {
746 if (!maps
.append(curMap
)) {
751 // Use two cursors: readMapCursor/readIndexCursor iterates over all properties
752 // starting at the first one, to search for the next non-hole entry.
753 // writeMapCursor/writeIndexCursor is used to write all non-hole keys.
755 // At the start of the loop, these cursors point to the next property slot to
758 size_t readMapCursorVectorIndex
= maps
.length() - 1;
759 DictionaryPropMap
* readMapCursor
= maps
[readMapCursorVectorIndex
];
760 uint32_t readIndexCursor
= 0;
762 size_t writeMapCursorVectorIndex
= readMapCursorVectorIndex
;
763 DictionaryPropMap
* writeMapCursor
= readMapCursor
;
764 uint32_t writeIndexCursor
= 0;
766 mozilla::DebugOnly
<uint32_t> numHoles
= 0;
769 if (readMapCursor
->hasKey(readIndexCursor
)) {
770 // Found a non-hole entry, copy it to its new position and update the
771 // PropMapTable to point to this new entry. Only do this if the read and
772 // write positions are different from each other.
773 if (readMapCursor
!= writeMapCursor
||
774 readIndexCursor
!= writeIndexCursor
) {
775 PropertyKey key
= readMapCursor
->getKey(readIndexCursor
);
776 auto p
= table
->lookupRaw(key
);
778 MOZ_ASSERT(p
->map() == readMapCursor
);
779 MOZ_ASSERT(p
->index() == readIndexCursor
);
781 writeMapCursor
->setKey(writeIndexCursor
, key
);
782 writeMapCursor
->linkedData_
.propInfos
[writeIndexCursor
] =
783 readMapCursor
->linkedData_
.propInfos
[readIndexCursor
];
785 PropMapAndIndex
newEntry(writeMapCursor
, writeIndexCursor
);
786 table
->replaceEntry(p
, key
, newEntry
);
788 // Advance the write cursor.
790 if (writeIndexCursor
== PropMap::Capacity
) {
791 MOZ_ASSERT(writeMapCursorVectorIndex
> 0);
792 writeMapCursorVectorIndex
--;
793 writeMapCursor
= maps
[writeMapCursorVectorIndex
];
794 writeIndexCursor
= 0;
799 // Advance the read cursor. If there are no more maps to read from, we're
802 if (readIndexCursor
== PropMap::Capacity
) {
803 if (readMapCursorVectorIndex
== 0) {
806 readMapCursorVectorIndex
--;
807 readMapCursor
= maps
[readMapCursorVectorIndex
];
812 // Sanity check: the read cursor skipped holes between properties and holes
813 // at the end of the last map (these are not included in holeCount_).
814 MOZ_ASSERT(map
->holeCount_
+ (PropMap::Capacity
- *mapLength
) == numHoles
);
816 // The write cursor points to the next available slot. If this is at the start
817 // of a new map, use the previous map (which must be full) instead.
818 if (writeIndexCursor
== 0 && writeMapCursor
->previous()) {
819 writeMapCursor
= writeMapCursor
->previous();
820 *mapLength
= PropMap::Capacity
;
822 *mapLength
= writeIndexCursor
;
825 // Ensure the last map does not have any keys in [mapLength, Capacity).
826 for (uint32_t i
= *mapLength
; i
< PropMap::Capacity
; i
++) {
827 writeMapCursor
->clearProperty(i
);
830 if (writeMapCursor
!= map
) {
831 map
->handOffLastMapStateTo(writeMapCursor
);
832 map
.set(writeMapCursor
);
836 MOZ_ASSERT(*mapLength
<= PropMap::Capacity
);
837 MOZ_ASSERT_IF(*mapLength
== 0, !map
->previous());
838 MOZ_ASSERT_IF(!map
->previous(), table
->entryCount() == *mapLength
);
841 void SharedPropMap::fixupAfterMovingGC() {
842 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
843 if (childrenRef
.isNone()) {
847 if (!hasChildrenSet()) {
848 SharedPropMapAndIndex child
= childrenRef
.toSingleChild();
849 if (gc::IsForwarded(child
.map())) {
850 child
= SharedPropMapAndIndex(gc::Forwarded(child
.map()), child
.index());
851 childrenRef
.setSingleChild(child
);
856 SharedChildrenSet
* set
= childrenRef
.toChildrenSet();
857 for (SharedChildrenSet::Enum
e(*set
); !e
.empty(); e
.popFront()) {
858 SharedPropMapAndIndex child
= e
.front();
859 if (IsForwarded(child
.map())) {
860 child
= SharedPropMapAndIndex(Forwarded(child
.map()), child
.index());
861 e
.mutableFront() = child
;
866 void SharedPropMap::removeChild(JS::GCContext
* gcx
, SharedPropMap
* child
) {
867 SharedPropMapAndIndex
& parentRef
= child
->treeDataRef().parent
;
868 MOZ_ASSERT(parentRef
.map() == this);
870 uint32_t index
= parentRef
.index();
873 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
874 MOZ_ASSERT(!childrenRef
.isNone());
876 if (!hasChildrenSet()) {
877 MOZ_ASSERT(childrenRef
.toSingleChild().map() == child
);
878 MOZ_ASSERT(childrenRef
.toSingleChild().index() == index
);
879 childrenRef
.setNone();
883 SharedChildrenSet
* set
= childrenRef
.toChildrenSet();
885 uint32_t nextIndex
= SharedPropMap::indexOfNextProperty(index
);
886 SharedChildrenHasher::Lookup
lookup(
887 child
->getPropertyInfoWithKey(nextIndex
), index
);
888 auto p
= set
->lookup(lookup
);
889 MOZ_ASSERT(p
, "Child must be in children set");
893 MOZ_ASSERT(set
->count() >= 1);
895 if (set
->count() == 1) {
896 // Convert from set form back to single child form.
897 SharedChildrenSet::Range r
= set
->all();
898 SharedPropMapAndIndex remainingChild
= r
.front();
899 childrenRef
.setSingleChild(remainingChild
);
900 clearHasChildrenSet();
901 gcx
->delete_(this, set
, MemoryUse::PropMapChildren
);
905 void LinkedPropMap::purgeTable(JS::GCContext
* gcx
) {
906 MOZ_ASSERT(hasTable());
907 gcx
->delete_(this, data_
.table
, MemoryUse::PropMapTable
);
908 data_
.table
= nullptr;
911 uint32_t PropMap::approximateEntryCount() const {
912 // Returns a number that's guaranteed to tbe >= the exact number of properties
913 // in this map (including previous maps). This is used to reserve space in the
914 // HashSet when allocating a table for this map.
916 const PropMap
* map
= this;
918 JS::AutoCheckCannotGC nogc
;
920 if (!map
->hasPrevious()) {
921 return count
+ PropMap::Capacity
;
923 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
924 return count
+ table
->entryCount();
926 count
+= PropMap::Capacity
;
927 map
= map
->asLinked()->previous();
931 bool PropMapTable::init(JSContext
* cx
, LinkedPropMap
* map
) {
932 if (!set_
.reserve(map
->approximateEntryCount())) {
933 ReportOutOfMemory(cx
);
937 PropMap
* curMap
= map
;
939 for (uint32_t i
= 0; i
< PropMap::Capacity
; i
++) {
940 if (curMap
->hasKey(i
)) {
941 PropertyKey key
= curMap
->getKey(i
);
942 set_
.putNewInfallible(key
, PropMapAndIndex(curMap
, i
));
945 if (!curMap
->hasPrevious()) {
948 curMap
= curMap
->asLinked()->previous();
954 void PropMapTable::trace(JSTracer
* trc
) {
957 for (Set::Enum
e(set_
); !e
.empty(); e
.popFront()) {
958 PropMap
* map
= e
.front().map();
959 TraceManuallyBarrieredEdge(trc
, &map
, "PropMapTable map");
960 if (map
!= e
.front().map()) {
961 e
.mutableFront() = PropMapAndIndex(map
, e
.front().index());
966 #ifdef JSGC_HASH_TABLE_CHECKS
967 void PropMapTable::checkAfterMovingGC() {
968 for (Set::Enum
e(set_
); !e
.empty(); e
.popFront()) {
969 PropMap
* map
= e
.front().map();
971 CheckGCThingAfterMovingGC(map
);
973 PropertyKey key
= map
->getKey(e
.front().index());
974 MOZ_RELEASE_ASSERT(!key
.isVoid());
976 auto p
= lookupRaw(key
);
977 MOZ_RELEASE_ASSERT(p
.found() && *p
== e
.front());
983 bool LinkedPropMap::canSkipMarkingTable() {
988 PropMapTable
* table
= data_
.table
;
993 for (uint32_t i
= 0; i
< Capacity
; i
++) {
994 if (map
->hasKey(i
)) {
995 PropertyKey key
= map
->getKey(i
);
996 PropMapTable::Ptr p
= table
->readonlyThreadsafeLookup(key
);
997 MOZ_ASSERT(*p
== PropMapAndIndex(map
, i
));
1001 if (!map
->hasPrevious()) {
1004 map
= map
->asLinked()->previous();
1007 return count
== table
->entryCount();
1011 bool LinkedPropMap::createTable(JSContext
* cx
) {
1012 MOZ_ASSERT(canHaveTable());
1013 MOZ_ASSERT(!hasTable());
1015 UniquePtr
<PropMapTable
> table
= cx
->make_unique
<PropMapTable
>();
1020 if (!table
->init(cx
, this)) {
1024 data_
.table
= table
.release();
1025 // TODO: The contents of PropMapTable is not currently tracked, only the
1027 AddCellMemory(this, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
1032 void PropMap::dump(js::GenericPrinter
& out
) const {
1033 out
.printf("map @ 0x%p\n", this);
1034 out
.printf("previous: 0x%p\n",
1035 hasPrevious() ? asLinked()->previous() : nullptr);
1037 if (canHaveTable()) {
1038 out
.printf("table: 0x%p\n", asLinked()->data_
.table
);
1040 out
.printf("table: (too small for table)\n");
1044 out
.printf("type: shared\n");
1045 out
.printf(" compact: %s\n", isCompact() ? "yes" : "no");
1046 SharedPropMapAndIndex parent
= asShared()->treeDataRef().parent
;
1047 if (parent
.isNone()) {
1048 out
.printf(" parent: (none)\n");
1050 out
.printf(" parent: 0x%p [%u]\n", parent
.map(), parent
.index());
1053 const DictionaryPropMap
* dictMap
= asDictionary();
1054 out
.printf("type: dictionary\n");
1055 out
.printf(" freeList: %u\n", dictMap
->freeList_
);
1056 out
.printf(" holeCount: %u\n", dictMap
->holeCount_
);
1059 out
.printf("properties:\n");
1060 for (uint32_t i
= 0; i
< Capacity
; i
++) {
1061 out
.printf(" %u: ", i
);
1064 out
.printf("(empty)\n");
1068 PropertyKey key
= getKey(i
);
1070 out
.printf("[%d]", key
.toInt());
1071 } else if (key
.isAtom()) {
1072 EscapedStringPrinter(out
, key
.toAtom(), '"');
1074 MOZ_ASSERT(key
.isSymbol());
1075 key
.toSymbol()->dump(out
);
1078 PropertyInfo prop
= getPropertyInfo(i
);
1079 out
.printf(" slot %u flags 0x%x ", prop
.maybeSlot(), prop
.flags().toRaw());
1081 if (!prop
.flags().isEmpty()) {
1083 auto dumpFlag
= [&](PropertyFlag flag
, const char* name
) {
1084 if (!prop
.flags().hasFlag(flag
)) {
1094 dumpFlag(PropertyFlag::Enumerable
, "enumerable");
1095 dumpFlag(PropertyFlag::Configurable
, "configurable");
1096 dumpFlag(PropertyFlag::Writable
, "writable");
1097 dumpFlag(PropertyFlag::AccessorProperty
, "accessor");
1098 dumpFlag(PropertyFlag::CustomDataProperty
, "custom-data");
1105 void PropMap::dump() const {
1106 Fprinter
out(stderr
);
1110 void PropMap::checkConsistency(NativeObject
* obj
) const {
1111 const uint32_t mapLength
= obj
->shape()->propMapLength();
1112 MOZ_ASSERT(mapLength
<= PropMap::Capacity
);
1114 JS::AutoCheckCannotGC nogc
;
1115 if (isDictionary()) {
1116 // Check dictionary slot free list.
1117 for (uint32_t fslot
= asDictionary()->freeList();
1118 fslot
!= SHAPE_INVALID_SLOT
;
1119 fslot
= obj
->getSlot(fslot
).toPrivateUint32()) {
1120 MOZ_ASSERT(fslot
< obj
->slotSpan());
1123 auto* table
= asLinked()->maybeTable(nogc
);
1124 const DictionaryPropMap
* curMap
= asDictionary();
1125 uint32_t numHoles
= 0;
1127 // Some fields must only be set for the last dictionary map.
1128 if (curMap
!= this) {
1129 MOZ_ASSERT(!curMap
->asLinked()->hasTable());
1130 MOZ_ASSERT(curMap
->holeCount_
== 0);
1131 MOZ_ASSERT(curMap
->freeList_
== SHAPE_INVALID_SLOT
);
1134 for (uint32_t i
= 0; i
< PropMap::Capacity
; i
++) {
1135 if (!curMap
->hasKey(i
)) {
1136 if (curMap
!= this || i
< mapLength
) {
1142 // The last dictionary map must only have keys up to mapLength.
1143 MOZ_ASSERT_IF(curMap
== this, i
< mapLength
);
1145 PropertyInfo prop
= curMap
->getPropertyInfo(i
);
1146 MOZ_ASSERT_IF(prop
.hasSlot(), prop
.slot() < obj
->slotSpan());
1148 // All properties must be in the table.
1150 PropertyKey key
= curMap
->getKey(i
);
1151 auto p
= table
->lookupRaw(key
);
1152 MOZ_ASSERT(p
->map() == curMap
);
1153 MOZ_ASSERT(p
->index() == i
);
1156 curMap
= curMap
->previous();
1159 MOZ_ASSERT(asDictionary()->holeCount_
== numHoles
);
1163 MOZ_ASSERT(mapLength
> 0);
1165 const SharedPropMap
* curMap
= asShared();
1167 curMap
->canHaveTable() ? curMap
->asLinked()->maybeTable(nogc
) : nullptr;
1169 // Shared maps without a previous map never have a table.
1170 MOZ_ASSERT_IF(!curMap
->hasPrevious(), !curMap
->canHaveTable());
1172 const SharedPropMap
* nextMap
= nullptr;
1173 mozilla::Maybe
<uint32_t> nextSlot
;
1175 // Verify numPreviousMaps is set correctly.
1176 MOZ_ASSERT_IF(nextMap
&& nextMap
->numPreviousMaps() != NumPreviousMapsMax
,
1177 curMap
->numPreviousMaps() + 1 == nextMap
->numPreviousMaps());
1178 MOZ_ASSERT(curMap
->hasPrevious() == (curMap
->numPreviousMaps() > 0));
1180 // If a previous map also has a table, it must have fewer entries than the
1181 // last map's table.
1182 if (table
&& curMap
!= this && curMap
->canHaveTable()) {
1183 if (auto* table2
= curMap
->asLinked()->maybeTable(nogc
)) {
1184 MOZ_ASSERT(table2
->entryCount() < table
->entryCount());
1188 for (int32_t i
= PropMap::Capacity
- 1; i
>= 0; i
--) {
1189 uint32_t index
= uint32_t(i
);
1191 // Only the last map can have holes, for entries following mapLength.
1192 if (!curMap
->hasKey(index
)) {
1193 MOZ_ASSERT(index
> 0);
1194 MOZ_ASSERT(curMap
== this);
1195 MOZ_ASSERT(index
>= mapLength
);
1199 // Check slot numbers are within slot span and never decreasing.
1200 PropertyInfo prop
= curMap
->getPropertyInfo(i
);
1201 if (prop
.hasSlot()) {
1202 MOZ_ASSERT_IF((curMap
!= this || index
< mapLength
),
1203 prop
.slot() < obj
->slotSpan());
1204 MOZ_ASSERT_IF(nextSlot
.isSome(), *nextSlot
>= prop
.slot());
1205 nextSlot
= mozilla::Some(prop
.slot());
1208 // All properties must be in the table.
1210 PropertyKey key
= curMap
->getKey(index
);
1211 auto p
= table
->lookupRaw(key
);
1212 MOZ_ASSERT(p
->map() == curMap
);
1213 MOZ_ASSERT(p
->index() == index
);
1217 if (!curMap
->hasPrevious()) {
1221 curMap
= curMap
->asLinked()->previous()->asShared();
1226 JS::ubi::Node::Size
JS::ubi::Concrete
<PropMap
>::size(
1227 mozilla::MallocSizeOf mallocSizeOf
) const {
1228 Size size
= js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1229 size_t children
= 0;
1231 get().addSizeOfExcludingThis(mallocSizeOf
, &children
, &tables
);
1232 return size
+ children
+ tables
;