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 "js/Printer.h" // js::GenericPrinter, js::Fprinter
12 #include "vm/JSObject.h"
13 #include "vm/JSONPrinter.h" // JSONPrinter
15 #include "gc/GCContext-inl.h"
16 #include "gc/Marking-inl.h"
17 #include "vm/JSContext-inl.h"
18 #include "vm/ObjectFlags-inl.h"
22 void PropMap::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf
,
23 size_t* children
, size_t* tables
) const {
24 if (isShared() && asShared()->hasChildrenSet()) {
25 auto* set
= asShared()->treeDataRef().children
.toChildrenSet();
26 *children
+= set
->shallowSizeOfIncludingThis(mallocSizeOf
);
28 if (canHaveTable() && asLinked()->hasTable()) {
29 *tables
+= asLinked()->data_
.table
->sizeOfIncludingThis(mallocSizeOf
);
34 SharedPropMap
* SharedPropMap::create(JSContext
* cx
, Handle
<SharedPropMap
*> prev
,
35 HandleId id
, PropertyInfo prop
) {
36 // If the first property has a slot number <= MaxSlotNumber, all properties
37 // added later will have a slot number <= CompactPropertyInfo::MaxSlotNumber
38 // so we can use a CompactPropMap.
39 static constexpr size_t MaxFirstSlot
=
40 CompactPropertyInfo::MaxSlotNumber
- (PropMap::Capacity
- 1);
42 if (!prev
&& prop
.maybeSlot() <= MaxFirstSlot
) {
43 return cx
->newCell
<CompactPropMap
>(id
, prop
);
46 return cx
->newCell
<NormalPropMap
>(prev
, id
, prop
);
50 SharedPropMap
* SharedPropMap::createInitial(JSContext
* cx
, HandleId id
,
52 // Lookup or create a shared map based on the first property.
54 using Lookup
= InitialPropMapHasher::Lookup
;
56 auto& table
= cx
->zone()->shapeZone().initialPropMaps
;
58 auto p
= MakeDependentAddPtr(cx
, table
, Lookup(id
, prop
));
63 SharedPropMap
* result
= create(cx
, /* prev = */ nullptr, id
, prop
);
68 Lookup
lookup(id
, prop
);
69 if (!p
.add(cx
, table
, lookup
, result
)) {
77 SharedPropMap
* SharedPropMap::clone(JSContext
* cx
, Handle
<SharedPropMap
*> map
,
79 MOZ_ASSERT(length
> 0);
81 if (map
->isCompact()) {
82 Rooted
<CompactPropMap
*> prev(cx
, map
->asCompact());
83 return cx
->newCell
<CompactPropMap
>(prev
, length
);
86 Rooted
<NormalPropMap
*> prev(cx
, map
->asNormal());
87 return cx
->newCell
<NormalPropMap
>(prev
, length
);
91 DictionaryPropMap
* SharedPropMap::toDictionaryMap(JSContext
* cx
,
92 Handle
<SharedPropMap
*> map
,
94 // Starting at the last map, clone each shared map to a new dictionary map.
96 Rooted
<DictionaryPropMap
*> lastDictMap(cx
);
97 Rooted
<DictionaryPropMap
*> nextDictMap(cx
);
99 Rooted
<SharedPropMap
*> sharedMap(cx
, map
);
100 uint32_t sharedLength
= length
;
102 sharedMap
->setHadDictionaryConversion();
104 DictionaryPropMap
* dictMap
;
105 if (sharedMap
->isCompact()) {
106 Rooted
<CompactPropMap
*> prev(cx
, sharedMap
->asCompact());
107 dictMap
= cx
->newCell
<DictionaryPropMap
>(prev
, sharedLength
);
109 Rooted
<NormalPropMap
*> prev(cx
, sharedMap
->asNormal());
110 dictMap
= cx
->newCell
<DictionaryPropMap
>(prev
, sharedLength
);
117 lastDictMap
= dictMap
;
121 nextDictMap
->initPrevious(dictMap
);
123 nextDictMap
= dictMap
;
125 if (!sharedMap
->hasPrevious()) {
128 sharedMap
= sharedMap
->asNormal()->previous();
129 sharedLength
= PropMap::Capacity
;
135 static MOZ_ALWAYS_INLINE SharedPropMap
* PropMapChildReadBarrier(
136 SharedPropMap
* parent
, SharedPropMap
* child
) {
137 JS::Zone
* zone
= child
->zone();
138 if (zone
->needsIncrementalBarrier()) {
139 // We need a read barrier for the map tree, since these are weak
145 if (MOZ_UNLIKELY(zone
->isGCSweeping() &&
146 IsAboutToBeFinalizedUnbarriered(child
))) {
147 // The map we've found is unreachable and due to be finalized, so
148 // remove our weak reference to it and don't use it.
149 MOZ_ASSERT(parent
->isMarkedAny());
150 parent
->removeChild(zone
->runtimeFromMainThread()->gcContext(), child
);
154 // We don't yield to the mutator when the zone is in this state so we don't
155 // need to account for it here.
156 MOZ_ASSERT(!zone
->isGCCompacting());
161 SharedPropMap
* SharedPropMap::lookupChild(uint32_t length
, HandleId id
,
163 MOZ_ASSERT(length
> 0);
165 SharedChildrenPtr children
= treeDataRef().children
;
166 if (children
.isNone()) {
170 if (!hasChildrenSet()) {
171 SharedPropMapAndIndex prevChild
= children
.toSingleChild();
172 if (prevChild
.index() == length
- 1) {
173 SharedPropMap
* child
= prevChild
.map();
174 uint32_t newPropIndex
= indexOfNextProperty(length
- 1);
175 if (child
->matchProperty(newPropIndex
, id
, prop
)) {
176 return PropMapChildReadBarrier(this, child
);
182 SharedChildrenSet
* set
= children
.toChildrenSet();
183 SharedChildrenHasher::Lookup
lookup(id
, prop
, length
- 1);
184 if (auto p
= set
->lookup(lookup
)) {
185 MOZ_ASSERT(p
->index() == length
- 1);
186 SharedPropMap
* child
= p
->map();
187 return PropMapChildReadBarrier(this, child
);
192 bool SharedPropMap::addChild(JSContext
* cx
, SharedPropMapAndIndex child
,
193 HandleId id
, PropertyInfo prop
) {
194 SharedPropMap
* childMap
= child
.map();
197 // If the parent map was full, the child map must have the parent as
198 // |previous| map. Else, the parent and child have the same |previous| map.
199 if (childMap
->hasPrevious()) {
200 if (child
.index() == PropMap::Capacity
- 1) {
201 MOZ_ASSERT(childMap
->asLinked()->previous() == this);
203 MOZ_ASSERT(childMap
->asLinked()->previous() == asLinked()->previous());
206 MOZ_ASSERT(!hasPrevious());
210 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
212 if (childrenRef
.isNone()) {
213 childrenRef
.setSingleChild(child
);
214 childMap
->treeDataRef().setParent(this, child
.index());
218 SharedChildrenHasher::Lookup
lookup(id
, prop
, child
.index());
220 if (hasChildrenSet()) {
221 if (!childrenRef
.toChildrenSet()->putNew(lookup
, child
)) {
222 ReportOutOfMemory(cx
);
226 auto hash
= MakeUnique
<SharedChildrenSet
>();
227 if (!hash
|| !hash
->reserve(2)) {
228 ReportOutOfMemory(cx
);
231 SharedPropMapAndIndex firstChild
= childrenRef
.toSingleChild();
232 SharedPropMap
* firstChildMap
= firstChild
.map();
233 uint32_t firstChildIndex
= indexOfNextProperty(firstChild
.index());
234 SharedChildrenHasher::Lookup
lookupFirst(
235 firstChildMap
->getPropertyInfoWithKey(firstChildIndex
),
237 hash
->putNewInfallible(lookupFirst
, firstChild
);
238 hash
->putNewInfallible(lookup
, child
);
240 childrenRef
.setChildrenSet(hash
.release());
242 AddCellMemory(this, sizeof(SharedChildrenSet
), MemoryUse::PropMapChildren
);
245 childMap
->treeDataRef().setParent(this, child
.index());
250 bool SharedPropMap::addProperty(JSContext
* cx
, const JSClass
* clasp
,
251 MutableHandle
<SharedPropMap
*> map
,
252 uint32_t* mapLength
, HandleId id
,
253 PropertyFlags flags
, ObjectFlags
* objectFlags
,
255 MOZ_ASSERT(!flags
.isCustomDataProperty());
257 *slot
= SharedPropMap::slotSpan(clasp
, map
, *mapLength
);
259 if (MOZ_UNLIKELY(*slot
> SHAPE_MAXIMUM_SLOT
)) {
260 ReportAllocationOverflow(cx
);
265 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
267 PropertyInfo prop
= PropertyInfo(flags
, *slot
);
268 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
272 bool SharedPropMap::addPropertyInReservedSlot(
273 JSContext
* cx
, const JSClass
* clasp
, MutableHandle
<SharedPropMap
*> map
,
274 uint32_t* mapLength
, HandleId id
, PropertyFlags flags
, uint32_t slot
,
275 ObjectFlags
* objectFlags
) {
276 MOZ_ASSERT(!flags
.isCustomDataProperty());
278 MOZ_ASSERT(slot
< JSCLASS_RESERVED_SLOTS(clasp
));
279 MOZ_ASSERT_IF(map
, map
->lastUsedSlot(*mapLength
) < slot
);
282 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
284 PropertyInfo prop
= PropertyInfo(flags
, slot
);
285 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
289 bool SharedPropMap::addPropertyWithKnownSlot(JSContext
* cx
,
290 const JSClass
* clasp
,
291 MutableHandle
<SharedPropMap
*> map
,
292 uint32_t* mapLength
, HandleId id
,
293 PropertyFlags flags
, uint32_t slot
,
294 ObjectFlags
* objectFlags
) {
295 MOZ_ASSERT(!flags
.isCustomDataProperty());
297 if (MOZ_UNLIKELY(slot
< JSCLASS_RESERVED_SLOTS(clasp
))) {
298 return addPropertyInReservedSlot(cx
, clasp
, map
, mapLength
, id
, flags
, slot
,
302 MOZ_ASSERT(slot
== SharedPropMap::slotSpan(clasp
, map
, *mapLength
));
303 MOZ_RELEASE_ASSERT(slot
<= SHAPE_MAXIMUM_SLOT
);
306 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
308 PropertyInfo prop
= PropertyInfo(flags
, slot
);
309 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
313 bool SharedPropMap::addCustomDataProperty(JSContext
* cx
, const JSClass
* clasp
,
314 MutableHandle
<SharedPropMap
*> map
,
315 uint32_t* mapLength
, HandleId id
,
317 ObjectFlags
* objectFlags
) {
318 MOZ_ASSERT(flags
.isCustomDataProperty());
320 // Custom data properties don't have a slot. Copy the last property's slot
321 // number to simplify the implementation of SharedPropMap::slotSpan.
322 uint32_t slot
= map
? map
->lastUsedSlot(*mapLength
) : SHAPE_INVALID_SLOT
;
325 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
327 PropertyInfo prop
= PropertyInfo(flags
, slot
);
328 return addPropertyInternal(cx
, map
, mapLength
, id
, prop
);
332 bool SharedPropMap::addPropertyInternal(JSContext
* cx
,
333 MutableHandle
<SharedPropMap
*> map
,
334 uint32_t* mapLength
, HandleId id
,
337 // Adding the first property.
338 MOZ_ASSERT(*mapLength
== 0);
339 map
.set(SharedPropMap::createInitial(cx
, id
, prop
));
347 MOZ_ASSERT(*mapLength
> 0);
349 if (*mapLength
< PropMap::Capacity
) {
350 // Use the next map entry if possible.
351 if (!map
->hasKey(*mapLength
)) {
352 if (map
->canHaveTable()) {
353 JS::AutoCheckCannotGC nogc
;
354 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
355 if (!table
->add(cx
, id
, PropMapAndIndex(map
, *mapLength
))) {
360 map
->initProperty(*mapLength
, id
, prop
);
364 if (map
->matchProperty(*mapLength
, id
, prop
)) {
369 // The next entry can't be used so look up or create a child map. The child
370 // map is a clone of this map up to mapLength, with the new property stored
371 // as the next entry.
373 if (SharedPropMap
* child
= map
->lookupChild(*mapLength
, id
, prop
)) {
379 SharedPropMap
* child
= SharedPropMap::clone(cx
, map
, *mapLength
);
383 child
->initProperty(*mapLength
, id
, prop
);
385 SharedPropMapAndIndex
childEntry(child
, *mapLength
- 1);
386 if (!map
->addChild(cx
, childEntry
, id
, prop
)) {
395 // This map is full so look up or create a child map.
396 MOZ_ASSERT(*mapLength
== PropMap::Capacity
);
398 if (SharedPropMap
* child
= map
->lookupChild(*mapLength
, id
, prop
)) {
404 SharedPropMap
* child
= SharedPropMap::create(cx
, map
, id
, prop
);
409 SharedPropMapAndIndex
childEntry(child
, PropMap::Capacity
- 1);
410 if (!map
->addChild(cx
, childEntry
, id
, prop
)) {
414 // As an optimization, pass the table to the new child map, unless adding the
415 // property to it OOMs. Measurements indicate this gets rid of a large number
416 // of PropMapTable allocations because we don't need to create a second table
417 // when the parent map won't be used again as last map.
418 if (map
->canHaveTable()) {
419 JS::AutoCheckCannotGC nogc
;
420 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
421 // Trigger a pre-barrier on the parent map to appease the pre-barrier
422 // verifier, because edges from the table are disappearing (even though
423 // these edges are strictly redundant with the |previous| maps).
424 gc::PreWriteBarrier(map
.get());
425 if (table
->add(cx
, id
, PropMapAndIndex(child
, 0))) {
426 map
->asLinked()->handOffTableTo(child
->asLinked());
428 cx
->recoverFromOutOfMemory();
438 static PropertyFlags
ComputeFlagsForSealOrFreeze(PropertyKey key
,
440 IntegrityLevel level
) {
441 // Private fields are not visible to SetIntegrityLevel.
442 if (key
.isSymbol() && key
.toSymbol()->isPrivateName()) {
446 // Make all properties non-configurable; if freezing, make data properties
448 flags
.clearFlag(PropertyFlag::Configurable
);
449 if (level
== IntegrityLevel::Frozen
&& flags
.isDataDescriptor()) {
450 flags
.clearFlag(PropertyFlag::Writable
);
457 bool SharedPropMap::freezeOrSealProperties(JSContext
* cx
, IntegrityLevel level
,
458 const JSClass
* clasp
,
459 MutableHandle
<SharedPropMap
*> map
,
461 ObjectFlags
* objectFlags
) {
462 // Add all maps to a Vector so we can iterate over them in reverse order
463 // (property definition order).
464 JS::RootedVector
<SharedPropMap
*> maps(cx
);
466 SharedPropMap
* curMap
= map
;
468 if (!maps
.append(curMap
)) {
471 if (!curMap
->hasPrevious()) {
474 curMap
= curMap
->asNormal()->previous();
478 // Build a new SharedPropMap by adding each property with the changed
480 Rooted
<SharedPropMap
*> newMap(cx
);
481 uint32_t newMapLength
= 0;
483 Rooted
<PropertyKey
> key(cx
);
484 Rooted
<SharedPropMap
*> curMap(cx
);
486 for (size_t i
= maps
.length(); i
> 0; i
--) {
487 curMap
= maps
[i
- 1];
488 uint32_t len
= (i
== 1) ? mapLength
: PropMap::Capacity
;
490 for (uint32_t j
= 0; j
< len
; j
++) {
491 key
= curMap
->getKey(j
);
492 PropertyInfo prop
= curMap
->getPropertyInfo(j
);
493 PropertyFlags flags
=
494 ComputeFlagsForSealOrFreeze(key
, prop
.flags(), level
);
496 if (prop
.isCustomDataProperty()) {
497 if (!addCustomDataProperty(cx
, clasp
, &newMap
, &newMapLength
, key
,
498 flags
, objectFlags
)) {
502 if (!addPropertyWithKnownSlot(cx
, clasp
, &newMap
, &newMapLength
, key
,
503 flags
, prop
.slot(), objectFlags
)) {
511 MOZ_ASSERT(newMapLength
== mapLength
);
515 void LinkedPropMap::handOffTableTo(LinkedPropMap
* next
) {
516 MOZ_ASSERT(hasTable());
517 MOZ_ASSERT(!next
->hasTable());
519 next
->data_
.table
= data_
.table
;
520 data_
.table
= nullptr;
522 // Note: for tables currently only sizeof(PropMapTable) is tracked.
523 RemoveCellMemory(this, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
524 AddCellMemory(next
, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
527 void DictionaryPropMap::handOffLastMapStateTo(DictionaryPropMap
* newLast
) {
528 // A dictionary object's last map contains the table, slot freeList, and
529 // holeCount. These fields always have their initial values for non-last maps.
531 MOZ_ASSERT(this != newLast
);
533 if (asLinked()->hasTable()) {
534 asLinked()->handOffTableTo(newLast
->asLinked());
537 MOZ_ASSERT(newLast
->freeList_
== SHAPE_INVALID_SLOT
);
538 newLast
->freeList_
= freeList_
;
539 freeList_
= SHAPE_INVALID_SLOT
;
541 MOZ_ASSERT(newLast
->holeCount_
== 0);
542 newLast
->holeCount_
= holeCount_
;
547 bool DictionaryPropMap::addProperty(JSContext
* cx
, const JSClass
* clasp
,
548 MutableHandle
<DictionaryPropMap
*> map
,
549 uint32_t* mapLength
, HandleId id
,
550 PropertyFlags flags
, uint32_t slot
,
551 ObjectFlags
* objectFlags
) {
555 GetObjectFlagsForNewProperty(clasp
, *objectFlags
, id
, flags
, cx
);
556 PropertyInfo prop
= PropertyInfo(flags
, slot
);
558 if (*mapLength
< PropMap::Capacity
) {
559 JS::AutoCheckCannotGC nogc
;
560 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
561 if (!table
->add(cx
, id
, PropMapAndIndex(map
, *mapLength
))) {
565 map
->initProperty(*mapLength
, id
, prop
);
570 DictionaryPropMap
* newMap
= cx
->newCell
<DictionaryPropMap
>(map
, id
, prop
);
575 JS::AutoCheckCannotGC nogc
;
576 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
577 if (!table
->add(cx
, id
, PropMapAndIndex(newMap
, 0))) {
582 MOZ_ASSERT(newMap
->previous() == map
);
583 map
->handOffLastMapStateTo(newMap
);
590 void DictionaryPropMap::changeProperty(JSContext
* cx
, const JSClass
* clasp
,
591 uint32_t index
, PropertyFlags flags
,
593 ObjectFlags
* objectFlags
) {
594 MOZ_ASSERT(hasKey(index
));
595 *objectFlags
= GetObjectFlagsForNewProperty(clasp
, *objectFlags
,
596 getKey(index
), flags
, cx
);
597 linkedData_
.propInfos
[index
] = PropertyInfo(flags
, slot
);
600 void DictionaryPropMap::freezeOrSealProperties(JSContext
* cx
,
601 IntegrityLevel level
,
602 const JSClass
* clasp
,
604 ObjectFlags
* objectFlags
) {
605 DictionaryPropMap
* curMap
= this;
607 for (uint32_t i
= 0; i
< mapLength
; i
++) {
608 if (!curMap
->hasKey(i
)) {
611 PropertyKey key
= curMap
->getKey(i
);
612 PropertyFlags flags
= curMap
->getPropertyInfo(i
).flags();
613 flags
= ComputeFlagsForSealOrFreeze(key
, flags
, level
);
614 curMap
->changePropertyFlags(cx
, clasp
, i
, flags
, objectFlags
);
616 curMap
= curMap
->previous();
617 mapLength
= PropMap::Capacity
;
622 void DictionaryPropMap::skipTrailingHoles(MutableHandle
<DictionaryPropMap
*> map
,
623 uint32_t* mapLength
) {
624 // After removing a property, rewind map/mapLength so that the last property
625 // is not a hole. This ensures accessing the last property of a map can always
626 // be done without checking for holes.
629 MOZ_ASSERT(*mapLength
> 0);
631 if (map
->hasKey(*mapLength
- 1)) {
636 } while (*mapLength
> 0);
638 if (!map
->previous()) {
639 // The dictionary map is empty, return the initial map with mapLength 0.
640 MOZ_ASSERT(*mapLength
== 0);
641 MOZ_ASSERT(map
->holeCount_
== 0);
645 map
->handOffLastMapStateTo(map
->previous());
646 map
.set(map
->previous());
647 *mapLength
= PropMap::Capacity
;
652 void DictionaryPropMap::removeProperty(JSContext
* cx
,
653 MutableHandle
<DictionaryPropMap
*> map
,
654 uint32_t* mapLength
, PropMapTable
* table
,
655 PropMapTable::Ptr
& ptr
) {
657 MOZ_ASSERT(*mapLength
> 0);
659 JS::AutoCheckCannotGC nogc
;
660 MOZ_ASSERT(map
->asLinked()->maybeTable(nogc
) == table
);
662 bool removingLast
= (map
== ptr
->map() && *mapLength
- 1 == ptr
->index());
663 ptr
->map()->asDictionary()->clearProperty(ptr
->index());
668 skipTrailingHoles(map
, mapLength
);
670 maybeCompact(cx
, map
, mapLength
);
674 void DictionaryPropMap::densifyElements(JSContext
* cx
,
675 MutableHandle
<DictionaryPropMap
*> map
,
679 MOZ_ASSERT(*mapLength
> 0);
681 JS::AutoCheckCannotGC nogc
;
682 PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
);
684 DictionaryPropMap
* currentMap
= map
;
685 uint32_t currentLen
= *mapLength
;
687 for (uint32_t i
= 0; i
< currentLen
; i
++) {
688 PropertyKey key
= currentMap
->getKey(i
);
690 if (!IdIsIndex(key
, &index
)) {
694 // The caller must have checked all sparse elements are plain data
696 PropertyInfo prop
= currentMap
->getPropertyInfo(i
);
697 MOZ_ASSERT(prop
.flags() == PropertyFlags::defaultDataPropFlags
);
699 uint32_t slot
= prop
.slot();
700 Value value
= obj
->getSlot(slot
);
701 obj
->setDenseElement(index
, value
);
702 obj
->freeDictionarySlot(slot
);
705 PropMapTable::Ptr p
= table
->lookupRaw(key
);
710 currentMap
->clearProperty(i
);
713 currentMap
= currentMap
->previous();
714 currentLen
= PropMap::Capacity
;
715 } while (currentMap
);
717 skipTrailingHoles(map
, mapLength
);
718 maybeCompact(cx
, map
, mapLength
);
721 void DictionaryPropMap::maybeCompact(JSContext
* cx
,
722 MutableHandle
<DictionaryPropMap
*> map
,
723 uint32_t* mapLength
) {
724 // If there are no holes, there's nothing to compact.
725 if (map
->holeCount_
== 0) {
729 JS::AutoCheckCannotGC nogc
;
730 PropMapTable
* table
= map
->asLinked()->ensureTable(cx
, nogc
);
732 // Compacting is optional so just return.
733 cx
->recoverFromOutOfMemory();
737 // Heuristic: only compact if the number of holes >= the number of (non-hole)
739 if (map
->holeCount_
< table
->entryCount()) {
743 // Add all dictionary maps to a Vector so that we can iterate over them in
744 // reverse order (property definition order). If appending to the Vector OOMs,
745 // just return because compacting is optional.
746 Vector
<DictionaryPropMap
*, 32, SystemAllocPolicy
> maps
;
747 for (DictionaryPropMap
* curMap
= map
; curMap
; curMap
= curMap
->previous()) {
748 if (!maps
.append(curMap
)) {
753 // Use two cursors: readMapCursor/readIndexCursor iterates over all properties
754 // starting at the first one, to search for the next non-hole entry.
755 // writeMapCursor/writeIndexCursor is used to write all non-hole keys.
757 // At the start of the loop, these cursors point to the next property slot to
760 size_t readMapCursorVectorIndex
= maps
.length() - 1;
761 DictionaryPropMap
* readMapCursor
= maps
[readMapCursorVectorIndex
];
762 uint32_t readIndexCursor
= 0;
764 size_t writeMapCursorVectorIndex
= readMapCursorVectorIndex
;
765 DictionaryPropMap
* writeMapCursor
= readMapCursor
;
766 uint32_t writeIndexCursor
= 0;
768 mozilla::DebugOnly
<uint32_t> numHoles
= 0;
771 if (readMapCursor
->hasKey(readIndexCursor
)) {
772 // Found a non-hole entry, copy it to its new position and update the
773 // PropMapTable to point to this new entry. Only do this if the read and
774 // write positions are different from each other.
775 if (readMapCursor
!= writeMapCursor
||
776 readIndexCursor
!= writeIndexCursor
) {
777 PropertyKey key
= readMapCursor
->getKey(readIndexCursor
);
778 auto p
= table
->lookupRaw(key
);
780 MOZ_ASSERT(p
->map() == readMapCursor
);
781 MOZ_ASSERT(p
->index() == readIndexCursor
);
783 writeMapCursor
->setKey(writeIndexCursor
, key
);
784 writeMapCursor
->linkedData_
.propInfos
[writeIndexCursor
] =
785 readMapCursor
->linkedData_
.propInfos
[readIndexCursor
];
787 PropMapAndIndex
newEntry(writeMapCursor
, writeIndexCursor
);
788 table
->replaceEntry(p
, key
, newEntry
);
790 // Advance the write cursor.
792 if (writeIndexCursor
== PropMap::Capacity
) {
793 MOZ_ASSERT(writeMapCursorVectorIndex
> 0);
794 writeMapCursorVectorIndex
--;
795 writeMapCursor
= maps
[writeMapCursorVectorIndex
];
796 writeIndexCursor
= 0;
801 // Advance the read cursor. If there are no more maps to read from, we're
804 if (readIndexCursor
== PropMap::Capacity
) {
805 if (readMapCursorVectorIndex
== 0) {
808 readMapCursorVectorIndex
--;
809 readMapCursor
= maps
[readMapCursorVectorIndex
];
814 // Sanity check: the read cursor skipped holes between properties and holes
815 // at the end of the last map (these are not included in holeCount_).
816 MOZ_ASSERT(map
->holeCount_
+ (PropMap::Capacity
- *mapLength
) == numHoles
);
818 // The write cursor points to the next available slot. If this is at the start
819 // of a new map, use the previous map (which must be full) instead.
820 if (writeIndexCursor
== 0 && writeMapCursor
->previous()) {
821 writeMapCursor
= writeMapCursor
->previous();
822 *mapLength
= PropMap::Capacity
;
824 *mapLength
= writeIndexCursor
;
827 // Ensure the last map does not have any keys in [mapLength, Capacity).
828 for (uint32_t i
= *mapLength
; i
< PropMap::Capacity
; i
++) {
829 writeMapCursor
->clearProperty(i
);
832 if (writeMapCursor
!= map
) {
833 map
->handOffLastMapStateTo(writeMapCursor
);
834 map
.set(writeMapCursor
);
838 MOZ_ASSERT(*mapLength
<= PropMap::Capacity
);
839 MOZ_ASSERT_IF(*mapLength
== 0, !map
->previous());
840 MOZ_ASSERT_IF(!map
->previous(), table
->entryCount() == *mapLength
);
843 void SharedPropMap::fixupAfterMovingGC() {
844 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
845 if (childrenRef
.isNone()) {
849 if (!hasChildrenSet()) {
850 SharedPropMapAndIndex child
= childrenRef
.toSingleChild();
851 if (gc::IsForwarded(child
.map())) {
852 child
= SharedPropMapAndIndex(gc::Forwarded(child
.map()), child
.index());
853 childrenRef
.setSingleChild(child
);
858 SharedChildrenSet
* set
= childrenRef
.toChildrenSet();
859 for (SharedChildrenSet::Enum
e(*set
); !e
.empty(); e
.popFront()) {
860 SharedPropMapAndIndex child
= e
.front();
861 if (IsForwarded(child
.map())) {
862 child
= SharedPropMapAndIndex(Forwarded(child
.map()), child
.index());
863 e
.mutableFront() = child
;
868 void SharedPropMap::removeChild(JS::GCContext
* gcx
, SharedPropMap
* child
) {
869 SharedPropMapAndIndex
& parentRef
= child
->treeDataRef().parent
;
870 MOZ_ASSERT(parentRef
.map() == this);
872 uint32_t index
= parentRef
.index();
875 SharedChildrenPtr
& childrenRef
= treeDataRef().children
;
876 MOZ_ASSERT(!childrenRef
.isNone());
878 if (!hasChildrenSet()) {
879 MOZ_ASSERT(childrenRef
.toSingleChild().map() == child
);
880 MOZ_ASSERT(childrenRef
.toSingleChild().index() == index
);
881 childrenRef
.setNone();
885 SharedChildrenSet
* set
= childrenRef
.toChildrenSet();
887 uint32_t nextIndex
= SharedPropMap::indexOfNextProperty(index
);
888 SharedChildrenHasher::Lookup
lookup(
889 child
->getPropertyInfoWithKey(nextIndex
), index
);
890 auto p
= set
->lookup(lookup
);
891 MOZ_ASSERT(p
, "Child must be in children set");
895 MOZ_ASSERT(set
->count() >= 1);
897 if (set
->count() == 1) {
898 // Convert from set form back to single child form.
899 SharedChildrenSet::Range r
= set
->all();
900 SharedPropMapAndIndex remainingChild
= r
.front();
901 childrenRef
.setSingleChild(remainingChild
);
902 clearHasChildrenSet();
903 gcx
->delete_(this, set
, MemoryUse::PropMapChildren
);
907 void LinkedPropMap::purgeTable(JS::GCContext
* gcx
) {
908 MOZ_ASSERT(hasTable());
909 gcx
->delete_(this, data_
.table
, MemoryUse::PropMapTable
);
910 data_
.table
= nullptr;
913 uint32_t PropMap::approximateEntryCount() const {
914 // Returns a number that's guaranteed to tbe >= the exact number of properties
915 // in this map (including previous maps). This is used to reserve space in the
916 // HashSet when allocating a table for this map.
918 const PropMap
* map
= this;
920 JS::AutoCheckCannotGC nogc
;
922 if (!map
->hasPrevious()) {
923 return count
+ PropMap::Capacity
;
925 if (PropMapTable
* table
= map
->asLinked()->maybeTable(nogc
)) {
926 return count
+ table
->entryCount();
928 count
+= PropMap::Capacity
;
929 map
= map
->asLinked()->previous();
933 bool PropMapTable::init(JSContext
* cx
, LinkedPropMap
* map
) {
934 if (!set_
.reserve(map
->approximateEntryCount())) {
935 ReportOutOfMemory(cx
);
939 PropMap
* curMap
= map
;
941 for (uint32_t i
= 0; i
< PropMap::Capacity
; i
++) {
942 if (curMap
->hasKey(i
)) {
943 PropertyKey key
= curMap
->getKey(i
);
944 set_
.putNewInfallible(key
, PropMapAndIndex(curMap
, i
));
947 if (!curMap
->hasPrevious()) {
950 curMap
= curMap
->asLinked()->previous();
956 void PropMapTable::trace(JSTracer
* trc
) {
959 for (Set::Enum
e(set_
); !e
.empty(); e
.popFront()) {
960 PropMap
* map
= e
.front().map();
961 TraceManuallyBarrieredEdge(trc
, &map
, "PropMapTable map");
962 if (map
!= e
.front().map()) {
963 e
.mutableFront() = PropMapAndIndex(map
, e
.front().index());
968 #ifdef JSGC_HASH_TABLE_CHECKS
969 void PropMapTable::checkAfterMovingGC() {
970 for (Set::Enum
e(set_
); !e
.empty(); e
.popFront()) {
971 PropMap
* map
= e
.front().map();
973 CheckGCThingAfterMovingGC(map
);
975 PropertyKey key
= map
->getKey(e
.front().index());
976 MOZ_RELEASE_ASSERT(!key
.isVoid());
978 auto p
= lookupRaw(key
);
979 MOZ_RELEASE_ASSERT(p
.found() && *p
== e
.front());
985 bool LinkedPropMap::canSkipMarkingTable() {
990 PropMapTable
* table
= data_
.table
;
995 for (uint32_t i
= 0; i
< Capacity
; i
++) {
996 if (map
->hasKey(i
)) {
997 PropertyKey key
= map
->getKey(i
);
998 PropMapTable::Ptr p
= table
->readonlyThreadsafeLookup(key
);
999 MOZ_ASSERT(*p
== PropMapAndIndex(map
, i
));
1003 if (!map
->hasPrevious()) {
1006 map
= map
->asLinked()->previous();
1009 return count
== table
->entryCount();
1013 bool LinkedPropMap::createTable(JSContext
* cx
) {
1014 MOZ_ASSERT(canHaveTable());
1015 MOZ_ASSERT(!hasTable());
1017 UniquePtr
<PropMapTable
> table
= cx
->make_unique
<PropMapTable
>();
1022 if (!table
->init(cx
, this)) {
1026 data_
.table
= table
.release();
1027 // TODO: The contents of PropMapTable is not currently tracked, only the
1029 AddCellMemory(this, sizeof(PropMapTable
), MemoryUse::PropMapTable
);
1033 #if defined(DEBUG) || defined(JS_JITSPEW)
1034 void PropMap::dump() const {
1035 Fprinter
out(stderr
);
1039 void PropMap::dump(js::GenericPrinter
& out
) const {
1040 js::JSONPrinter
json(out
);
1045 void PropMap::dump(js::JSONPrinter
& json
) const {
1051 template <typename KnownF
, typename UnknownF
>
1052 void ForEachPropertyFlag(PropertyFlags flags
, KnownF known
, UnknownF unknown
) {
1053 uint8_t raw
= flags
.toRaw();
1054 for (uint8_t i
= 1; i
; i
= i
<< 1) {
1058 switch (PropertyFlag(raw
& i
)) {
1059 case PropertyFlag::Configurable
:
1060 known("Configurable");
1062 case PropertyFlag::Enumerable
:
1063 known("Enumerable");
1065 case PropertyFlag::Writable
:
1068 case PropertyFlag::AccessorProperty
:
1069 known("AccessorProperty");
1071 case PropertyFlag::CustomDataProperty
:
1072 known("UseWatchtowerTestingCallback");
1081 template <typename KnownF
, typename UnknownF
>
1083 void PropMap::forEachPropMapFlag(uintptr_t flags
, KnownF known
,
1085 for (uintptr_t i
= 1 << gc::CellFlagBitsReservedForGC
;
1086 i
< 1 << PropMap::NumPreviousMapsShift
; i
= i
<< 1) {
1090 switch (flags
& i
) {
1091 case PropMap::Flags::IsCompactFlag
:
1092 known("IsCompactFlag");
1094 case PropMap::Flags::HasPrevFlag
:
1095 known("HasPrevFlag");
1097 case PropMap::Flags::IsDictionaryFlag
:
1098 known("IsDictionaryFlag");
1100 case PropMap::Flags::CanHaveTableFlag
:
1101 known("CanHaveTableFlag");
1103 case PropMap::Flags::HasChildrenSetFlag
:
1104 known("HasChildrenSetFlag");
1106 case PropMap::Flags::HadDictionaryConversionFlag
:
1107 known("HadDictionaryConversionFlag");
1116 const char* PropMapTypeToString(const js::PropMap
* map
) {
1117 if (map
->isLinked()) {
1118 return "js::LinkedPropMap";
1121 if (map
->isShared()) {
1122 if (map
->isCompact()) {
1123 return "js::CompactPropMap";
1125 return "js::NormalPropMap";
1128 return "js::DictionaryPropMap";
1131 void PropMap::dumpFields(js::JSONPrinter
& json
) const {
1132 json
.formatProperty("address", "(%s*)0x%p", PropMapTypeToString(this), this);
1134 json
.beginInlineListProperty("flags");
1136 flags(), [&](const char* name
) { json
.value("%s", name
); },
1137 [&](uint32_t value
) { json
.value("Unknown(%08x)", value
); });
1138 json
.endInlineList();
1141 asLinked()->dumpOwnFields(json
);
1142 } else if (isShared()) {
1143 asShared()->dumpOwnFields(json
);
1145 asDictionary()->dumpOwnFields(json
);
1148 json
.beginObjectProperty("properties");
1149 for (uint32_t i
= 0; i
< Capacity
; i
++) {
1151 SprintfLiteral(name
, "%u", i
);
1154 json
.nullProperty(name
);
1158 json
.beginObjectProperty(name
);
1159 dumpFieldsAt(json
, i
);
1165 void LinkedPropMap::dumpOwnFields(js::JSONPrinter
& json
) const {
1166 if (hasPrevious()) {
1167 json
.formatProperty("previous", "(%s*)0x%p",
1168 PropMapTypeToString(previous()), previous());
1171 if (canHaveTable()) {
1172 json
.formatProperty("table", "(js::PropMapTable*)0x%p", data_
.table
);
1176 void SharedPropMap::dumpOwnFields(js::JSONPrinter
& json
) const {
1177 SharedPropMapAndIndex parent
= treeDataRef().parent
;
1178 if (parent
.isNone()) {
1179 json
.nullProperty("parent");
1181 json
.formatProperty("parent", "(%s*)0x%p [%u]",
1182 PropMapTypeToString(parent
.map()), parent
.map(),
1187 void DictionaryPropMap::dumpOwnFields(js::JSONPrinter
& json
) const {
1188 json
.property("freeList", freeList_
);
1189 json
.property("holeCount", holeCount_
);
1192 void PropMap::dumpFieldsAt(js::JSONPrinter
& json
, uint32_t index
) const {
1193 PropertyKey key
= getKey(index
);
1194 js::GenericPrinter
& out
= json
.beginStringProperty("key");
1195 key
.dumpStringContent(out
);
1196 json
.endStringProperty();
1198 PropertyInfo prop
= getPropertyInfo(index
);
1199 json
.beginInlineListProperty("flags");
1200 ForEachPropertyFlag(
1201 prop
.flags(), [&](const char* name
) { json
.value("%s", name
); },
1202 [&](uint8_t value
) { json
.value("Unknown(%02x)", value
); });
1203 json
.endInlineList();
1205 if (prop
.hasSlot()) {
1206 json
.property("slot", prop
.slot());
1210 void PropMap::dumpDescriptorStringContentAt(js::GenericPrinter
& out
,
1211 uint32_t index
) const {
1212 PropertyInfo prop
= getPropertyInfo(index
);
1214 out
.printf("map=(%s*)0x%p, index=%u", PropMapTypeToString(this), this, index
);
1216 if (prop
.enumerable()) {
1217 out
.put(", enumerable");
1219 if (prop
.configurable()) {
1220 out
.put(", configurable");
1222 if (prop
.isDataDescriptor() && prop
.writable()) {
1223 out
.put(", writable");
1226 if (prop
.isCustomDataProperty()) {
1227 out
.printf(", <custom-data-prop>");
1230 if (prop
.hasSlot()) {
1231 out
.printf(", slot=%u", prop
.slot());
1235 JS::UniqueChars
PropMap::getPropertyNameAt(uint32_t index
) const {
1241 PropertyKey key
= getKey(index
);
1242 key
.dumpPropertyName(sp
);
1244 return sp
.release();
1246 #endif // defined(DEBUG) || defined(JS_JITSPEW)
1249 void PropMap::checkConsistency(NativeObject
* obj
) const {
1250 const uint32_t mapLength
= obj
->shape()->propMapLength();
1251 MOZ_ASSERT(mapLength
<= PropMap::Capacity
);
1253 JS::AutoCheckCannotGC nogc
;
1254 if (isDictionary()) {
1255 // Check dictionary slot free list.
1256 for (uint32_t fslot
= asDictionary()->freeList();
1257 fslot
!= SHAPE_INVALID_SLOT
;
1258 fslot
= obj
->getSlot(fslot
).toPrivateUint32()) {
1259 MOZ_ASSERT(fslot
< obj
->slotSpan());
1262 auto* table
= asLinked()->maybeTable(nogc
);
1263 const DictionaryPropMap
* curMap
= asDictionary();
1264 uint32_t numHoles
= 0;
1266 // Some fields must only be set for the last dictionary map.
1267 if (curMap
!= this) {
1268 MOZ_ASSERT(!curMap
->asLinked()->hasTable());
1269 MOZ_ASSERT(curMap
->holeCount_
== 0);
1270 MOZ_ASSERT(curMap
->freeList_
== SHAPE_INVALID_SLOT
);
1273 for (uint32_t i
= 0; i
< PropMap::Capacity
; i
++) {
1274 if (!curMap
->hasKey(i
)) {
1275 if (curMap
!= this || i
< mapLength
) {
1281 // The last dictionary map must only have keys up to mapLength.
1282 MOZ_ASSERT_IF(curMap
== this, i
< mapLength
);
1284 PropertyInfo prop
= curMap
->getPropertyInfo(i
);
1285 MOZ_ASSERT_IF(prop
.hasSlot(), prop
.slot() < obj
->slotSpan());
1287 // All properties must be in the table.
1289 PropertyKey key
= curMap
->getKey(i
);
1290 auto p
= table
->lookupRaw(key
);
1291 MOZ_ASSERT(p
->map() == curMap
);
1292 MOZ_ASSERT(p
->index() == i
);
1295 curMap
= curMap
->previous();
1298 MOZ_ASSERT(asDictionary()->holeCount_
== numHoles
);
1302 MOZ_ASSERT(mapLength
> 0);
1304 const SharedPropMap
* curMap
= asShared();
1306 curMap
->canHaveTable() ? curMap
->asLinked()->maybeTable(nogc
) : nullptr;
1308 // Shared maps without a previous map never have a table.
1309 MOZ_ASSERT_IF(!curMap
->hasPrevious(), !curMap
->canHaveTable());
1311 const SharedPropMap
* nextMap
= nullptr;
1312 mozilla::Maybe
<uint32_t> nextSlot
;
1314 // Verify numPreviousMaps is set correctly.
1315 MOZ_ASSERT_IF(nextMap
&& nextMap
->numPreviousMaps() != NumPreviousMapsMax
,
1316 curMap
->numPreviousMaps() + 1 == nextMap
->numPreviousMaps());
1317 MOZ_ASSERT(curMap
->hasPrevious() == (curMap
->numPreviousMaps() > 0));
1319 // If a previous map also has a table, it must have fewer entries than the
1320 // last map's table.
1321 if (table
&& curMap
!= this && curMap
->canHaveTable()) {
1322 if (auto* table2
= curMap
->asLinked()->maybeTable(nogc
)) {
1323 MOZ_ASSERT(table2
->entryCount() < table
->entryCount());
1327 for (int32_t i
= PropMap::Capacity
- 1; i
>= 0; i
--) {
1328 uint32_t index
= uint32_t(i
);
1330 // Only the last map can have holes, for entries following mapLength.
1331 if (!curMap
->hasKey(index
)) {
1332 MOZ_ASSERT(index
> 0);
1333 MOZ_ASSERT(curMap
== this);
1334 MOZ_ASSERT(index
>= mapLength
);
1338 // Check slot numbers are within slot span and never decreasing.
1339 PropertyInfo prop
= curMap
->getPropertyInfo(i
);
1340 if (prop
.hasSlot()) {
1341 MOZ_ASSERT_IF((curMap
!= this || index
< mapLength
),
1342 prop
.slot() < obj
->slotSpan());
1343 MOZ_ASSERT_IF(nextSlot
.isSome(), *nextSlot
>= prop
.slot());
1344 nextSlot
= mozilla::Some(prop
.slot());
1347 // All properties must be in the table.
1349 PropertyKey key
= curMap
->getKey(index
);
1350 auto p
= table
->lookupRaw(key
);
1351 MOZ_ASSERT(p
->map() == curMap
);
1352 MOZ_ASSERT(p
->index() == index
);
1356 if (!curMap
->hasPrevious()) {
1360 curMap
= curMap
->asLinked()->previous()->asShared();
1365 JS::ubi::Node::Size
JS::ubi::Concrete
<PropMap
>::size(
1366 mozilla::MallocSizeOf mallocSizeOf
) const {
1367 Size size
= js::gc::Arena::thingSize(get().asTenured().getAllocKind());
1368 size_t children
= 0;
1370 get().addSizeOfExcludingThis(mallocSizeOf
, &children
, &tables
);
1371 return size
+ children
+ tables
;