Bug 1706900 part 6 - Replace ShapeTable's custom hash table with a HashSet. r=jonco

This is much simpler and lets us tidy up the addProperty code more. It also makes
it easier to change ShapeTable for property maps in the future.

Lookup performance and memory usage appear to be similar for the two versions,
probably because ShapeTable used the same double-hashing algorithm and because
we can purge most ShapeTables on GC.

Differential Revision: https://phabricator.services.mozilla.com/D113089
This commit is contained in:
Jan de Mooij 2021-04-26 11:00:06 +00:00
parent 409b24beaa
commit dca62ba9ee
6 changed files with 162 additions and 521 deletions

View File

@ -194,9 +194,8 @@ void js::NativeObject::checkShapeConsistency() {
while (shape->parent) {
MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
ShapeTable::Entry& entry =
table->search<MaybeAdding::NotAdding>(shape->propid(), nogc);
MOZ_ASSERT(entry.shape() == shape);
ShapeTable::Ptr p = table->search(shape->propid(), nogc);
MOZ_ASSERT(*p == shape);
shape = shape->parent;
}
}
@ -219,9 +218,8 @@ void js::NativeObject::checkShapeConsistency() {
if (ShapeTable* table = shape->maybeTable(nogc)) {
MOZ_ASSERT(shape->parent);
for (Shape::Range<NoGC> r(shape); !r.empty(); r.popFront()) {
ShapeTable::Entry& entry =
table->search<MaybeAdding::NotAdding>(r.front().propid(), nogc);
MOZ_ASSERT(entry.shape() == &r.front());
ShapeTable::Ptr p = table->search(r.front().propid(), nogc);
MOZ_ASSERT(*p == &r.front());
}
}
if (prev) {

View File

@ -949,9 +949,8 @@ class NativeObject : public JSObject {
JSContext* cx, HandleNativeObject obj, HandleShape parent,
MutableHandle<StackShape> child);
static MOZ_ALWAYS_INLINE bool maybeConvertToOrGrowDictionaryForAdd(
JSContext* cx, HandleNativeObject obj, HandleId id, ShapeTable** table,
ShapeTable::Entry** entry, const AutoKeepShapeCaches& keep);
static MOZ_ALWAYS_INLINE bool maybeConvertToDictionaryForAdd(
JSContext* cx, HandleNativeObject obj);
static bool maybeToDictionaryModeForChange(JSContext* cx,
HandleNativeObject obj,

View File

@ -54,36 +54,34 @@ MOZ_ALWAYS_INLINE bool Shape::maybeCreateCacheForLookup(JSContext* cx) {
return Shape::cachify(cx, this);
}
template <MaybeAdding Adding>
/* static */ inline bool Shape::search(JSContext* cx, Shape* start, jsid id,
const AutoKeepShapeCaches& keep,
Shape** pshape, ShapeTable** ptable,
ShapeTable::Entry** pentry) {
ShapeTable::Ptr* pptr) {
if (start->inDictionary()) {
ShapeTable* table = start->ensureTableForDictionary(cx, keep);
if (!table) {
return false;
}
*ptable = table;
*pentry = &table->search<Adding>(id, keep);
*pshape = (*pentry)->shape();
*pptr = table->search(id, keep);
*pshape = *pptr ? **pptr : nullptr;
return true;
}
*ptable = nullptr;
*pentry = nullptr;
*pshape = Shape::search<Adding>(cx, start, id);
*pptr = ShapeTable::Ptr();
*pshape = Shape::search(cx, start, id);
return true;
}
template <MaybeAdding Adding>
/* static */ MOZ_ALWAYS_INLINE Shape* Shape::search(JSContext* cx, Shape* start,
jsid id) {
Shape* foundShape = nullptr;
if (start->maybeCreateCacheForLookup(cx)) {
JS::AutoCheckCannotGC nogc;
ShapeCachePtr cache = start->getCache(nogc);
if (cache.search<Adding>(id, start, &foundShape)) {
if (cache.search(id, start, &foundShape)) {
return foundShape;
}
} else {
@ -219,115 +217,6 @@ static inline uint8_t GetPropertyAttributes(JSObject* obj,
return prop.shapeProperty().attributes();
}
/*
* Double hashing needs the second hash code to be relatively prime to table
* size, so we simply make hash2 odd.
*/
MOZ_ALWAYS_INLINE HashNumber Hash1(HashNumber hash0, uint32_t shift) {
return hash0 >> shift;
}
MOZ_ALWAYS_INLINE HashNumber Hash2(HashNumber hash0, uint32_t log2,
uint32_t shift) {
return ((hash0 << log2) >> shift) | 1;
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::searchUnchecked(jsid id) {
MOZ_ASSERT(entries_);
MOZ_ASSERT(!JSID_IS_EMPTY(id));
/* Compute the primary hash address. */
HashNumber hash0 = HashId(id);
HashNumber hash1 = Hash1(hash0, hashShift_);
Entry* entry = &getEntry(hash1);
/* Miss: return space for a new entry. */
if (entry->isFree()) {
return *entry;
}
/* Hit: return entry. */
Shape* shape = entry->shape();
if (shape && shape->propidRaw() == id) {
return *entry;
}
/* Collision: double hash. */
uint32_t sizeLog2 = HASH_BITS - hashShift_;
HashNumber hash2 = Hash2(hash0, sizeLog2, hashShift_);
uint32_t sizeMask = BitMask(sizeLog2);
/* Save the first removed entry pointer so we can recycle it if adding. */
Entry* firstRemoved;
if (Adding == MaybeAdding::Adding) {
if (entry->isRemoved()) {
firstRemoved = entry;
} else {
firstRemoved = nullptr;
if (!entry->hadCollision()) {
entry->flagCollision();
}
}
}
#ifdef DEBUG
bool collisionFlag = true;
if (!entry->isRemoved()) {
collisionFlag = entry->hadCollision();
}
#endif
while (true) {
hash1 -= hash2;
hash1 &= sizeMask;
entry = &getEntry(hash1);
if (entry->isFree()) {
return (Adding == MaybeAdding::Adding && firstRemoved) ? *firstRemoved
: *entry;
}
shape = entry->shape();
if (shape && shape->propidRaw() == id) {
MOZ_ASSERT(collisionFlag);
return *entry;
}
if (Adding == MaybeAdding::Adding) {
if (entry->isRemoved()) {
if (!firstRemoved) {
firstRemoved = entry;
}
} else {
if (!entry->hadCollision()) {
entry->flagCollision();
}
}
}
#ifdef DEBUG
if (!entry->isRemoved()) {
collisionFlag &= entry->hadCollision();
}
#endif
}
MOZ_CRASH("Shape::search failed to find an expected entry.");
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::search(
jsid id, const AutoKeepShapeCaches&) {
return searchUnchecked<Adding>(id);
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE ShapeTable::Entry& ShapeTable::search(
jsid id, const JS::AutoCheckCannotGC&) {
return searchUnchecked<Adding>(id);
}
/*
* Keep this function in sync with search. It neither hashifies the start
* shape nor increments linear search count.
@ -340,7 +229,7 @@ MOZ_ALWAYS_INLINE Shape* Shape::searchNoHashify(Shape* start, jsid id) {
Shape* foundShape;
JS::AutoCheckCannotGC nogc;
ShapeCachePtr cache = start->getCache(nogc);
if (!cache.search<MaybeAdding::NotAdding>(id, start, &foundShape)) {
if (!cache.search(id, start, &foundShape)) {
foundShape = start->searchLinear(id);
}

View File

@ -36,9 +36,6 @@ using mozilla::PodZero;
using JS::AutoCheckCannotGC;
Shape* const ShapeTable::Entry::SHAPE_REMOVED =
(Shape*)ShapeTable::Entry::SHAPE_COLLISION;
bool ShapeIC::init(JSContext* cx) {
size_ = MAX_SIZE;
entries_.reset(cx->pod_calloc<Entry>(size_));
@ -46,40 +43,16 @@ bool ShapeIC::init(JSContext* cx) {
}
bool ShapeTable::init(JSContext* cx, Shape* lastProp) {
uint32_t sizeLog2 = CeilingLog2Size(entryCount_);
uint32_t size = Bit(sizeLog2);
if (entryCount_ >= size - (size >> 2)) {
sizeLog2++;
}
if (sizeLog2 < MIN_SIZE_LOG2) {
sizeLog2 = MIN_SIZE_LOG2;
}
size = Bit(sizeLog2);
entries_.reset(cx->pod_calloc<Entry>(size));
if (!entries_) {
if (!set_.reserve(lastProp->entryCount())) {
ReportOutOfMemory(cx);
return false;
}
MOZ_ASSERT(sizeLog2 <= HASH_BITS);
hashShift_ = HASH_BITS - sizeLog2;
for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) {
Shape& shape = r.front();
Entry& entry = searchUnchecked<MaybeAdding::Adding>(shape.propid());
/*
* Beware duplicate args and arg vs. var conflicts: the youngest shape
* (nearest to lastProp) must win. See bug 600067.
*/
if (!entry.shape()) {
entry.setPreservingCollision(&shape);
}
set_.putNewInfallible(shape.propidRaw(), &shape);
}
MOZ_ASSERT(capacity() == size);
MOZ_ASSERT(size >= MIN_SIZE);
MOZ_ASSERT(!needsToGrow());
return true;
}
@ -143,8 +116,7 @@ void Shape::handoffTableTo(Shape* shape) {
bool Shape::hashify(JSContext* cx, Shape* shape) {
MOZ_ASSERT(!shape->hasTable());
UniquePtr<ShapeTable> table =
cx->make_unique<ShapeTable>(shape->entryCount());
UniquePtr<ShapeTable> table = cx->make_unique<ShapeTable>();
if (!table) {
return false;
}
@ -192,65 +164,6 @@ bool Shape::cachify(JSContext* cx, Shape* shape) {
return true;
}
bool ShapeTable::change(JSContext* cx, int log2Delta) {
MOZ_ASSERT(entries_);
MOZ_ASSERT(-1 <= log2Delta && log2Delta <= 1);
/*
* Grow, shrink, or compress by changing this->entries_.
*/
uint32_t oldLog2 = HASH_BITS - hashShift_;
uint32_t newLog2 = oldLog2 + log2Delta;
uint32_t oldSize = Bit(oldLog2);
uint32_t newSize = Bit(newLog2);
Entry* newTable = cx->maybe_pod_calloc<Entry>(newSize);
if (!newTable) {
return false;
}
/* Now that we have newTable allocated, update members. */
MOZ_ASSERT(newLog2 <= HASH_BITS);
hashShift_ = HASH_BITS - newLog2;
removedCount_ = 0;
Entry* oldTable = entries_.release();
entries_.reset(newTable);
/* Copy only live entries, leaving removed and free ones behind. */
AutoCheckCannotGC nogc;
for (Entry* oldEntry = oldTable; oldSize != 0; oldEntry++) {
if (Shape* shape = oldEntry->shape()) {
Entry& entry = search<MaybeAdding::Adding>(shape->propid(), nogc);
MOZ_ASSERT(entry.isFree());
entry.setShape(shape);
}
oldSize--;
}
MOZ_ASSERT(capacity() == newSize);
/* Finally, free the old entries storage. */
js_free(oldTable);
return true;
}
bool ShapeTable::grow(JSContext* cx) {
MOZ_ASSERT(needsToGrow());
uint32_t size = capacity();
int delta = removedCount_ < (size >> 2);
MOZ_ASSERT(entryCount_ + removedCount_ <= size - 1);
if (!change(cx, delta)) {
if (entryCount_ + removedCount_ == size - 1) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
void ShapeCachePtr::trace(JSTracer* trc) {
if (isIC()) {
getICPointer()->trace(trc);
@ -269,14 +182,12 @@ void ShapeIC::trace(JSTracer* trc) {
}
void ShapeTable::trace(JSTracer* trc) {
for (size_t i = 0; i < capacity(); i++) {
Entry& entry = getEntry(i);
Shape* shape = entry.shape();
if (shape) {
TraceManuallyBarrieredEdge(trc, &shape, "ShapeTable shape");
if (shape != entry.shape()) {
entry.setPreservingCollision(shape);
}
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
Shape* shape = e.front();
MOZ_ASSERT(shape);
TraceManuallyBarrieredEdge(trc, &shape, "ShapeTable shape");
if (shape != e.front()) {
e.mutableFront() = shape;
}
}
}
@ -311,12 +222,10 @@ void ShapeIC::checkAfterMovingGC() {
}
void ShapeTable::checkAfterMovingGC() {
for (size_t i = 0; i < capacity(); i++) {
Entry& entry = getEntry(i);
Shape* shape = entry.shape();
if (shape) {
CheckGCThingAfterMovingGC(shape);
}
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
Shape* shape = e.front();
MOZ_ASSERT(shape);
CheckGCThingAfterMovingGC(shape);
}
}
@ -393,19 +302,36 @@ Shape* Shape::replaceLastProperty(JSContext* cx, ObjectFlags objectFlags,
}
if (obj->inDictionaryMode()) {
AutoKeepShapeCaches keep(cx);
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return nullptr;
}
MOZ_ASSERT(parent == obj->lastProperty());
Shape* shape = Allocate<Shape>(cx);
if (!shape) {
return nullptr;
}
// First try to add the shape to the table. If allocating slots below fails,
// we have to remove the entry again.
if (!table->add(cx, child.propid(), shape)) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
return nullptr;
}
if (child.slot() >= obj->slotSpan()) {
if (!obj->ensureSlotsForDictionaryObject(cx, child.slot() + 1)) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
table->remove(child.propid());
return nullptr;
}
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
// Pass the table along to the new last property.
MOZ_ASSERT(shape->previous()->maybeTable(keep) == table);
shape->previous()->handoffTableTo(shape);
return shape;
}
@ -433,13 +359,27 @@ Shape* Shape::replaceLastProperty(JSContext* cx, ObjectFlags objectFlags,
child.setSlot(parent->maybeSlot());
if (obj->inDictionaryMode()) {
AutoKeepShapeCaches keep(cx);
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return nullptr;
}
MOZ_ASSERT(parent == obj->lastProperty());
Shape* shape = Allocate<Shape>(cx);
if (!shape) {
return nullptr;
}
if (!table->add(cx, child.propid(), shape)) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
return nullptr;
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
// Pass the table along to the new last property.
MOZ_ASSERT(shape->previous()->maybeTable(keep) == table);
shape->previous()->handoffTableTo(shape);
return shape;
}
@ -553,49 +493,15 @@ class MOZ_RAII AutoCheckShapeConsistency {
} // namespace js
/* static */ MOZ_ALWAYS_INLINE bool
NativeObject::maybeConvertToOrGrowDictionaryForAdd(
JSContext* cx, HandleNativeObject obj, HandleId id, ShapeTable** table,
ShapeTable::Entry** entry, const AutoKeepShapeCaches& keep) {
MOZ_ASSERT(!!*table == !!*entry);
// The code below deals with either converting obj to dictionary mode or
// growing an object that's already in dictionary mode.
if (!obj->inDictionaryMode()) {
if (!ShouldConvertToDictionary(obj)) {
return true;
}
if (!toDictionaryMode(cx, obj)) {
return false;
}
*table = obj->lastProperty()->maybeTable(keep);
} else {
if (!(*table)->needsToGrow()) {
return true;
}
if (!(*table)->grow(cx)) {
return false;
}
NativeObject::maybeConvertToDictionaryForAdd(JSContext* cx,
HandleNativeObject obj) {
if (obj->inDictionaryMode()) {
return true;
}
*entry = &(*table)->search<MaybeAdding::Adding>(id, keep);
MOZ_ASSERT(!(*entry)->shape());
return true;
}
MOZ_ALWAYS_INLINE void Shape::updateDictionaryTable(
ShapeTable* table, ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep) {
MOZ_ASSERT(table);
MOZ_ASSERT(entry);
MOZ_ASSERT(inDictionary());
// Store this Shape in the table entry.
entry->setPreservingCollision(this);
table->incEntryCount();
// Pass the table along to the new last property, namely *this.
MOZ_ASSERT(parent->maybeTable(keep) == table);
parent->handoffTableTo(this);
if (!ShouldConvertToDictionary(obj)) {
return true;
}
return toDictionaryMode(cx, obj);
}
static void AssertValidCustomDataProp(NativeObject* obj, unsigned attrs) {
@ -616,19 +522,7 @@ bool NativeObject::addCustomDataProperty(JSContext* cx, HandleNativeObject obj,
AutoCheckShapeConsistency check(obj);
AssertValidCustomDataProp(obj, attrs);
AutoKeepShapeCaches keep(cx);
ShapeTable* table = nullptr;
ShapeTable::Entry* entry = nullptr;
if (obj->inDictionaryMode()) {
table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return false;
}
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry,
keep)) {
if (!maybeConvertToDictionaryForAdd(cx, obj)) {
return false;
}
@ -648,10 +542,6 @@ bool NativeObject::addCustomDataProperty(JSContext* cx, HandleNativeObject obj,
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
return true;
}
@ -676,19 +566,7 @@ bool NativeObject::addProperty(JSContext* cx, HandleNativeObject obj,
obj->isExtensible() ||
(JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))));
AutoKeepShapeCaches keep(cx);
ShapeTable* table = nullptr;
ShapeTable::Entry* entry = nullptr;
if (obj->inDictionaryMode()) {
table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return false;
}
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
if (!maybeConvertToOrGrowDictionaryForAdd(cx, obj, id, &table, &entry,
keep)) {
if (!maybeConvertToDictionaryForAdd(cx, obj)) {
return false;
}
@ -708,10 +586,6 @@ bool NativeObject::addProperty(JSContext* cx, HandleNativeObject obj,
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
*slotOut = shape->slot();
return true;
}
@ -789,38 +663,20 @@ bool NativeObject::addEnumerableDataProperty(JSContext* cx,
return obj->setLastPropertyForNewDataProperty(cx, child);
} while (0);
AutoKeepShapeCaches keep(cx);
ShapeTable* table = nullptr;
ShapeTable::Entry* entry = nullptr;
if (!obj->inDictionaryMode()) {
if (MOZ_UNLIKELY(ShouldConvertToDictionary(obj))) {
if (!toDictionaryMode(cx, obj)) {
return false;
}
table = obj->lastProperty()->maybeTable(keep);
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
} else {
table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return false;
}
if (table->needsToGrow()) {
if (!table->grow(cx)) {
return false;
}
}
entry = &table->search<MaybeAdding::Adding>(id, keep);
MOZ_ASSERT(!entry->shape());
if (!maybeConvertToDictionaryForAdd(cx, obj)) {
return false;
}
MOZ_ASSERT(!!table == !!entry);
/* Find or create a property tree node labeled by our arguments. */
Shape* shape;
if (obj->inDictionaryMode()) {
AutoKeepShapeCaches keep(cx);
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table) {
return false;
}
uint32_t slot;
if (!allocDictionarySlot(cx, obj, &slot)) {
return false;
@ -834,14 +690,25 @@ bool NativeObject::addEnumerableDataProperty(JSContext* cx,
if (!shape) {
return false;
}
// First try to add the shape to the table. If allocating slots below fails,
// we have to remove the entry again.
if (!table->add(cx, id, shape)) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
return false;
}
if (slot >= obj->slotSpan()) {
if (MOZ_UNLIKELY(!obj->ensureSlotsForDictionaryObject(cx, slot + 1))) {
new (shape) Shape(obj->lastProperty()->base(), ObjectFlags(), 0);
table->remove(id);
return false;
}
}
shape->initDictionaryShape(child, obj->numFixedSlots(),
DictionaryShapeLink(obj));
// Pass the table along to the new last property.
MOZ_ASSERT(shape->previous()->maybeTable(keep) == table);
shape->previous()->handoffTableTo(shape);
} else {
uint32_t slot = obj->slotSpan();
MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
@ -864,10 +731,6 @@ bool NativeObject::addEnumerableDataProperty(JSContext* cx,
MOZ_ASSERT(shape == obj->lastProperty());
if (table) {
shape->updateDictionaryTable(table, entry, keep);
}
*slotOut = shape->slot();
return true;
}
@ -930,8 +793,7 @@ bool NativeObject::maybeToDictionaryModeForChange(JSContext* cx,
AutoCheckCannotGC nogc;
ShapeTable* table = obj->lastProperty()->maybeTable(nogc);
MOZ_ASSERT(table);
shape.set(
table->search<MaybeAdding::NotAdding>(shape->propid(), nogc).shape());
shape.set(*table->search(shape->propid(), nogc));
return true;
}
@ -1108,10 +970,10 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
AutoKeepShapeCaches keep(cx);
ShapeTable* table;
ShapeTable::Entry* entry;
ShapeTable::Ptr ptr;
RootedShape shape(cx);
if (!Shape::search(cx, obj->lastProperty(), id, keep, shape.address(), &table,
&entry)) {
&ptr)) {
return false;
}
@ -1127,9 +989,9 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
if (!NativeObject::setHadGetterSetterChange(cx, obj)) {
return false;
}
// Relookup shape/table/entry in case setHadGetterSetterChange changed them.
// Relookup shape/table/ptr in case setHadGetterSetterChange changed them.
if (!Shape::search(cx, obj->lastProperty(), id, keep, shape.address(),
&table, &entry)) {
&table, &ptr)) {
return false;
}
}
@ -1147,8 +1009,8 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
}
table = obj->lastProperty()->maybeTable(keep);
MOZ_ASSERT(table);
entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep);
shape = entry->shape();
ptr = table->search(shape->propid(), keep);
shape = *ptr;
}
/*
@ -1179,28 +1041,7 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
*/
if (obj->inDictionaryMode()) {
MOZ_ASSERT(obj->lastProperty()->maybeTable(keep) == table);
if (entry->hadCollision()) {
entry->setRemoved();
table->decEntryCount();
table->incRemovedCount();
} else {
entry->setFree();
table->decEntryCount();
#ifdef DEBUG
/*
* Check the consistency of the table but limit the number of
* checks not to alter significantly the complexity of the
* delete in debug builds, see bug 534493.
*/
Shape* aprop = obj->lastProperty();
for (int n = 50; --n >= 0 && aprop->parent; aprop = aprop->parent) {
MOZ_ASSERT_IF(aprop != shape,
obj->containsPure(aprop->propid(), aprop->property()));
}
#endif
}
table->remove(ptr);
{
// Remove shape from its non-circular doubly linked list.
@ -1222,12 +1063,6 @@ bool NativeObject::removeProperty(JSContext* cx, HandleNativeObject obj,
/* Generate a new shape for the object, infallibly. */
MOZ_ALWAYS_TRUE(NativeObject::generateOwnShape(cx, obj, spare));
/* Consider shrinking table if its load factor is <= .25. */
uint32_t size = table->capacity();
if (size > ShapeTable::MIN_SIZE && table->entryCount() <= size >> 2) {
(void)table->change(cx, -1);
}
} else {
/*
* Non-dictionary-mode shape tables are shared immutables, so all we
@ -1271,11 +1106,6 @@ bool NativeObject::generateOwnShape(JSContext* cx, HandleNativeObject obj,
return false;
}
ShapeTable::Entry* entry =
oldShape->isEmptyShape()
? nullptr
: &table->search<MaybeAdding::NotAdding>(oldShape->propidRef(), nogc);
// Replace the old last-property shape with the new one.
StackShape nshape(oldShape);
newShape->initDictionaryShape(nshape, obj->numFixedSlots(),
@ -1287,9 +1117,12 @@ bool NativeObject::generateOwnShape(JSContext* cx, HandleNativeObject obj,
MOZ_ASSERT(newShape == obj->lastProperty());
oldShape->handoffTableTo(newShape);
if (entry) {
entry->setPreservingCollision(newShape);
if (!newShape->isEmptyShape()) {
ShapeTable::Ptr ptr = table->search(oldShape->propidRef(), nogc);
MOZ_ASSERT(*ptr == oldShape);
table->replaceShape(ptr, newShape->propidRaw(), newShape);
}
return true;
}
@ -1456,12 +1289,9 @@ bool Shape::canSkipMarkingShapeCache() {
uint32_t count = 0;
for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront()) {
Shape* shape = &r.front();
ShapeTable::Entry& entry =
cache.getTablePointer()->search<MaybeAdding::NotAdding>(shape->propid(),
nogc);
if (entry.isLive()) {
count++;
}
ShapeTable::Ptr p = cache.getTablePointer()->search(shape->propid(), nogc);
MOZ_ASSERT(*p == shape);
count++;
}
return count == cache.getTablePointer()->entryCount();

View File

@ -337,8 +337,6 @@ class PropertyTree {
class TenuringTracer;
enum class MaybeAdding { Adding = true, NotAdding = false };
class AutoKeepShapeCaches;
/*
@ -406,10 +404,6 @@ class ShapeIC {
UniquePtr<Entry[], JS::FreePolicy> entries_;
};
/*
* ShapeTable uses multiplicative hashing, but specialized to
* minimize footprint.
*/
class ShapeTable {
public:
friend class NativeObject;
@ -417,144 +411,71 @@ class ShapeTable {
friend class Shape;
friend class ShapeCachePtr;
class Entry {
// js::Shape pointer tag bit indicating a collision.
static const uintptr_t SHAPE_COLLISION = 1;
static Shape* const SHAPE_REMOVED; // = SHAPE_COLLISION
Shape* shape_;
Entry() = delete;
Entry(const Entry&) = delete;
Entry& operator=(const Entry&) = delete;
public:
bool isFree() const { return shape_ == nullptr; }
bool isRemoved() const { return shape_ == SHAPE_REMOVED; }
bool isLive() const { return !isFree() && !isRemoved(); }
bool hadCollision() const { return uintptr_t(shape_) & SHAPE_COLLISION; }
void setFree() { shape_ = nullptr; }
void setRemoved() { shape_ = SHAPE_REMOVED; }
Shape* shape() const {
return reinterpret_cast<Shape*>(uintptr_t(shape_) & ~SHAPE_COLLISION);
}
void setShape(Shape* shape) {
MOZ_ASSERT(isFree());
MOZ_ASSERT(shape);
MOZ_ASSERT(shape != SHAPE_REMOVED);
shape_ = shape;
MOZ_ASSERT(!hadCollision());
}
void flagCollision() {
shape_ = reinterpret_cast<Shape*>(uintptr_t(shape_) | SHAPE_COLLISION);
}
void setPreservingCollision(Shape* shape) {
shape_ = reinterpret_cast<Shape*>(uintptr_t(shape) |
uintptr_t(hadCollision()));
}
};
private:
static const uint32_t HASH_BITS = mozilla::tl::BitSize<HashNumber>::value;
struct Hasher : public DefaultHasher<Shape*> {
using Key = Shape*;
using Lookup = PropertyKey;
static MOZ_ALWAYS_INLINE HashNumber hash(PropertyKey key);
static MOZ_ALWAYS_INLINE bool match(Shape* shape, PropertyKey key);
};
using Set = HashSet<Shape*, Hasher, SystemAllocPolicy>;
Set set_;
// This value is low because it's common for a ShapeTable to be created
// with an entryCount of zero.
static const uint32_t MIN_SIZE_LOG2 = 2;
static const uint32_t MIN_SIZE = Bit(MIN_SIZE_LOG2);
// SHAPE_INVALID_SLOT or head of slot freelist in owning dictionary-mode
// object.
uint32_t freeList_ = SHAPE_INVALID_SLOT;
uint32_t hashShift_; /* multiplicative hash shift */
uint32_t entryCount_; /* number of entries in table */
uint32_t removedCount_; /* removed entry sentinels in table */
uint32_t freeList_; /* SHAPE_INVALID_SLOT or head of slot
freelist in owning dictionary-mode
object */
UniquePtr<Entry[], JS::FreePolicy>
entries_; /* table of ptrs to shared tree nodes */
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& searchUnchecked(jsid id);
public:
explicit ShapeTable(uint32_t nentries)
: hashShift_(HASH_BITS - MIN_SIZE_LOG2),
entryCount_(nentries),
removedCount_(0),
freeList_(SHAPE_INVALID_SLOT),
entries_(nullptr) {
/* NB: entries is set by init, which must be called. */
MOZ_ALWAYS_INLINE Set::Ptr searchUnchecked(jsid id) {
return set_.lookup(id);
}
public:
using Ptr = Set::Ptr;
ShapeTable() = default;
~ShapeTable() = default;
uint32_t entryCount() const { return entryCount_; }
uint32_t entryCount() const { return set_.count(); }
uint32_t freeList() const { return freeList_; }
void setFreeList(uint32_t slot) { freeList_ = slot; }
/*
* This counts the ShapeTable object itself (which must be
* heap-allocated) and its |entries| array.
*/
// This counts the ShapeTable object itself (which must be heap-allocated) and
// its HashSet.
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return mallocSizeOf(this) + mallocSizeOf(entries_.get());
return mallocSizeOf(this) + set_.shallowSizeOfExcludingThis(mallocSizeOf);
}
// init() is fallible and reports OOM to the context.
bool init(JSContext* cx, Shape* lastProp);
// change() is fallible but does not report OOM.
bool change(JSContext* cx, int log2Delta);
MOZ_ALWAYS_INLINE Set::Ptr search(jsid id, const AutoKeepShapeCaches&) {
return searchUnchecked(id);
}
MOZ_ALWAYS_INLINE Set::Ptr search(jsid id, const JS::AutoCheckCannotGC&) {
return searchUnchecked(id);
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& search(jsid id, const AutoKeepShapeCaches&);
bool add(JSContext* cx, PropertyKey key, Shape* shape) {
if (!set_.putNew(key, shape)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& search(jsid id, const JS::AutoCheckCannotGC&);
void remove(Ptr ptr) { set_.remove(ptr); }
void remove(PropertyKey key) { set_.remove(key); }
void replaceShape(Ptr ptr, PropertyKey key, Shape* newShape) {
MOZ_ASSERT(*ptr != newShape);
set_.replaceKey(ptr, key, newShape);
}
void trace(JSTracer* trc);
#ifdef JSGC_HASH_TABLE_CHECKS
void checkAfterMovingGC();
#endif
private:
Entry& getEntry(uint32_t i) const {
MOZ_ASSERT(i < capacity());
return entries_[i];
}
void decEntryCount() {
MOZ_ASSERT(entryCount_ > 0);
entryCount_--;
}
void incEntryCount() {
entryCount_++;
MOZ_ASSERT(entryCount_ + removedCount_ <= capacity());
}
void incRemovedCount() {
removedCount_++;
MOZ_ASSERT(entryCount_ + removedCount_ <= capacity());
}
// By definition, hashShift = HASH_BITS - log2(capacity).
uint32_t capacity() const { return Bit(HASH_BITS - hashShift_); }
// Whether we need to grow. We want to do this if the load factor
// is >= 0.75
bool needsToGrow() const {
uint32_t size = capacity();
return entryCount_ + removedCount_ >= size - (size >> 2);
}
// Try to grow the table. On failure, reports out of memory on cx
// and returns false. This will make any extant pointers into the
// table invalid. Don't call this unless needsToGrow() is true.
bool grow(JSContext* cx);
};
/*
@ -597,7 +518,6 @@ class ShapeCachePtr {
ShapeCachePtr() : p(0) {}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE bool search(jsid id, Shape* start, Shape** foundShape);
bool isIC() const { return (getType() == CacheType::IC); }
@ -970,14 +890,12 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
void clearDictionaryNextPtr();
void dictNextPreWriteBarrier();
template <MaybeAdding Adding = MaybeAdding::NotAdding>
static MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, Shape* start, jsid id);
template <MaybeAdding Adding = MaybeAdding::NotAdding>
[[nodiscard]] static inline bool search(JSContext* cx, Shape* start, jsid id,
const AutoKeepShapeCaches&,
Shape** pshape, ShapeTable** ptable,
ShapeTable::Entry** pentry);
ShapeTable::Ptr* pptr);
static inline Shape* searchNoHashify(Shape* start, jsid id);
@ -1018,10 +936,6 @@ class Shape : public gc::CellWithTenuredGCPointer<gc::TenuredCell, BaseShape> {
[[nodiscard]] MOZ_ALWAYS_INLINE bool maybeCreateCacheForLookup(JSContext* cx);
MOZ_ALWAYS_INLINE void updateDictionaryTable(ShapeTable* table,
ShapeTable::Entry* entry,
const AutoKeepShapeCaches& keep);
void setObjectFlags(ObjectFlags flags) {
MOZ_ASSERT(inDictionary());
objectFlags_ = flags;
@ -1619,7 +1533,6 @@ inline bool Shape::matches(const StackShape& other) const {
other.attrs);
}
template <MaybeAdding Adding>
MOZ_ALWAYS_INLINE bool ShapeCachePtr::search(jsid id, Shape* start,
Shape** foundShape) {
bool found = false;
@ -1628,8 +1541,8 @@ MOZ_ALWAYS_INLINE bool ShapeCachePtr::search(jsid id, Shape* start,
found = ic->search(id, foundShape);
} else if (isTable()) {
ShapeTable* table = getTablePointer();
ShapeTable::Entry& entry = table->searchUnchecked<Adding>(id);
*foundShape = entry.shape();
auto p = table->searchUnchecked(id);
*foundShape = p ? *p : nullptr;
found = true;
}
return found;
@ -1702,6 +1615,14 @@ class MOZ_RAII ShapePropertyIter {
FakePtr operator->() const { return {get()}; }
};
MOZ_ALWAYS_INLINE HashNumber ShapeTable::Hasher::hash(PropertyKey key) {
return HashId(key);
}
MOZ_ALWAYS_INLINE bool ShapeTable::Hasher::match(Shape* shape,
PropertyKey key) {
return shape->propidRaw() == key;
}
} // namespace js
// JS::ubi::Nodes can point to Shapes and BaseShapes; they're js::gc::Cell

View File

@ -664,12 +664,16 @@ class HashSet {
// Specifically, both HashPolicy::hash and HashPolicy::match must return
// identical results for the new and old key when applied against all
// possible matching values.
void replaceKey(Ptr aPtr, const T& aNewValue) {
void replaceKey(Ptr aPtr, const Lookup& aLookup, const T& aNewValue) {
MOZ_ASSERT(aPtr.found());
MOZ_ASSERT(*aPtr != aNewValue);
MOZ_ASSERT(HashPolicy::hash(*aPtr) == HashPolicy::hash(aNewValue));
MOZ_ASSERT(HashPolicy::match(*aPtr, aNewValue));
MOZ_ASSERT(HashPolicy::match(*aPtr, aLookup));
MOZ_ASSERT(HashPolicy::match(aNewValue, aLookup));
const_cast<T&>(*aPtr) = aNewValue;
MOZ_ASSERT(*lookup(aLookup) == aNewValue);
}
void replaceKey(Ptr aPtr, const T& aNewValue) {
replaceKey(aPtr, aNewValue, aNewValue);
}
// -- Iteration ------------------------------------------------------------