Bug 1782487 part 2 - Free dictionary slots if possible after densifying or removing properties. r=jonco

Dictionary slots currently never shrink: when removing properties we put the slots
on the dictionary free list. If we know there are no slotful properties left, however,
we can free the slots and reset the free list.

This is mostly nice for densifying because we can usually free all slots in that case.

The patch also fixes a minor issue in `NativeObject::shrinkSlots`, because this code
is now used for dictionary objects for the first time.

Differential Revision: https://phabricator.services.mozilla.com/D153437
This commit is contained in:
Jan de Mooij 2022-08-03 11:03:57 +00:00
parent a1d68e44d2
commit 1d6aa9252f
3 changed files with 41 additions and 3 deletions

View File

@ -384,8 +384,6 @@ void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
MOZ_ASSERT(newCapacity < oldCapacity);
MOZ_ASSERT(oldCapacity == getSlotsHeader()->capacity());
uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
ObjectSlots* oldHeaderSlots = ObjectSlots::fromSlots(slots_);
MOZ_ASSERT(oldHeaderSlots->capacity() == oldCapacity);
@ -395,12 +393,15 @@ void NativeObject::shrinkSlots(JSContext* cx, uint32_t oldCapacity,
size_t nbytes = ObjectSlots::allocSize(oldCapacity);
RemoveCellMemory(this, nbytes, MemoryUse::ObjectSlots);
FreeSlots(cx, this, oldHeaderSlots, nbytes);
setEmptyDynamicSlots(dictionarySpan);
// dictionarySlotSpan is initialized to the correct value by the callers.
setEmptyDynamicSlots(0);
return;
}
MOZ_ASSERT_IF(!is<ArrayObject>(), newCapacity >= SLOT_CAPACITY_MIN);
uint32_t dictionarySpan = getSlotsHeader()->dictionarySlotSpan();
uint32_t newAllocated = ObjectSlots::allocCount(newCapacity);
HeapSlot* allocation = ReallocateObjectBuffer<HeapSlot>(

View File

@ -678,6 +678,9 @@ class NativeObject : public JSObject {
*/
bool ensureSlotsForDictionaryObject(JSContext* cx, uint32_t span);
void maybeFreeDictionaryPropSlots(JSContext* cx, DictionaryPropMap* map,
uint32_t mapLength);
[[nodiscard]] static bool toDictionaryMode(JSContext* cx,
Handle<NativeObject*> obj);

View File

@ -667,6 +667,28 @@ bool NativeObject::changeCustomDataPropAttributes(JSContext* cx,
return true;
}
void NativeObject::maybeFreeDictionaryPropSlots(JSContext* cx,
DictionaryPropMap* map,
uint32_t mapLength) {
// We can free all non-reserved slots if there are no properties left. We also
// handle the case where there's a single slotless property, to support arrays
// (array.length is a custom data property).
MOZ_ASSERT(shape()->dictionaryPropMap() == map);
MOZ_ASSERT(shape()->propMapLength() == mapLength);
if (mapLength > 1 || map->previous()) {
return;
}
if (mapLength == 1 && map->getPropertyInfo(0).hasSlot()) {
return;
}
uint32_t numReserved = JSCLASS_RESERVED_SLOTS(getClass());
MOZ_ALWAYS_TRUE(ensureSlotsForDictionaryObject(cx, numReserved));
map->setFreeList(SHAPE_INVALID_SLOT);
}
/* static */
bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj,
HandleId id) {
@ -773,6 +795,15 @@ bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj,
obj->shape()->updateNewDictionaryShape(obj->shape()->objectFlags(), dictMap,
mapLength);
// If we just deleted the last property, consider shrinking the slots. We only
// do this if there are a lot of slots, to avoid allocating/freeing dynamic
// slots repeatedly.
static constexpr size_t MinSlotSpanForFree = 64;
if (obj->dictionaryModeSlotSpan() >= MinSlotSpanForFree) {
obj->maybeFreeDictionaryPropSlots(cx, dictMap, mapLength);
}
return true;
}
@ -800,6 +831,9 @@ bool NativeObject::densifySparseElements(JSContext* cx,
objectFlags.clearFlag(ObjectFlag::Indexed);
obj->shape()->updateNewDictionaryShape(objectFlags, map, mapLength);
obj->maybeFreeDictionaryPropSlots(cx, map, mapLength);
return true;
}