Bug 1314569 - Purge ShapeTables on shrinking GCs. r=jonco

This commit is contained in:
Jan de Mooij 2016-11-03 19:15:15 +01:00
Родитель a14435c813
Коммит af4e54557a
10 изменённых файлов: 319 добавлений и 157 удалений

Просмотреть файл

@ -199,9 +199,11 @@ static const PhaseInfo phases[] = {
{ PHASE_MARK_RUNTIME_DATA, "Mark Runtime-wide Data", PHASE_MARK_ROOTS, 52 },
{ PHASE_MARK_EMBEDDING, "Mark Embedding", PHASE_MARK_ROOTS, 53 },
{ PHASE_MARK_COMPARTMENTS, "Mark Compartments", PHASE_MARK_ROOTS, 54 },
{ PHASE_LIMIT, nullptr, PHASE_NO_PARENT, 59 }
{ PHASE_PURGE_SHAPE_TABLES, "Purge ShapeTables", PHASE_NO_PARENT, 60 },
// Current number of telemetryBuckets is 59. If you insert new phases
{ PHASE_LIMIT, nullptr, PHASE_NO_PARENT, 60 }
// Current number of telemetryBuckets is 60. If you insert new phases
// somewhere, start at that number and count up. Do not change any existing
// numbers.
};

Просмотреть файл

@ -88,6 +88,7 @@ enum Phase : uint8_t {
PHASE_MARK_RUNTIME_DATA,
PHASE_MARK_EMBEDDING,
PHASE_MARK_COMPARTMENTS,
PHASE_PURGE_SHAPE_TABLES,
PHASE_LIMIT,
PHASE_NONE = PHASE_LIMIT,
@ -144,6 +145,7 @@ struct ZoneGCStats
_(WaitBgThread, "waitBG", PHASE_WAIT_BACKGROUND_THREAD) \
_(DiscardCode, "discard", PHASE_MARK_DISCARD_CODE) \
_(RelazifyFunc, "relazify", PHASE_RELAZIFY_FUNCTIONS) \
_(PurgeTables, "purgeTables", PHASE_PURGE_SHAPE_TABLES) \
_(Purge, "purge", PHASE_PURGE) \
_(Mark, "mark", PHASE_MARK) \
_(Sweep, "sweep", PHASE_SWEEP) \

Просмотреть файл

@ -48,6 +48,7 @@ JS::Zone::Zone(JSRuntime* rt)
gcScheduled_(false),
gcPreserveCode_(false),
jitUsingBarriers_(false),
keepShapeTables_(false),
listNext_(NotOnList)
{
/* Ensure that there are no vtables to mess us up here. */

Просмотреть файл

@ -516,6 +516,13 @@ struct Zone : public JS::shadow::Zone,
void checkUniqueIdTableAfterMovingGC();
#endif
bool keepShapeTables() const {
return keepShapeTables_;
}
void setKeepShapeTables(bool b) {
keepShapeTables_ = b;
}
private:
js::jit::JitZone* jitZone_;
@ -523,6 +530,7 @@ struct Zone : public JS::shadow::Zone,
bool gcScheduled_;
bool gcPreserveCode_;
bool jitUsingBarriers_;
bool keepShapeTables_;
// Allow zones to be linked into a list
friend class js::gc::ZoneList;

Просмотреть файл

@ -3861,10 +3861,21 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
* the same functions over and over.
*/
if (invocationKind == GC_SHRINK) {
gcstats::AutoPhase ap(stats, gcstats::PHASE_RELAZIFY_FUNCTIONS);
{
gcstats::AutoPhase ap(stats, gcstats::PHASE_RELAZIFY_FUNCTIONS);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
RelazifyFunctions(zone, AllocKind::FUNCTION);
RelazifyFunctions(zone, AllocKind::FUNCTION_EXTENDED);
}
}
/* Purge ShapeTables. */
gcstats::AutoPhase ap(stats, gcstats::PHASE_PURGE_SHAPE_TABLES);
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
RelazifyFunctions(zone, AllocKind::FUNCTION);
RelazifyFunctions(zone, AllocKind::FUNCTION_EXTENDED);
if (zone->keepShapeTables())
continue;
for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next())
baseShape->maybePurgeTable();
}
}
@ -7167,9 +7178,10 @@ js::gc::CheckHashTablesAfterMovingGC(JSRuntime* rt)
zone->checkInitialShapesTableAfterMovingGC();
zone->checkBaseShapeTableAfterMovingGC();
JS::AutoCheckCannotGC nogc;
for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next()) {
if (baseShape->hasTable())
baseShape->table().checkAfterMovingGC();
if (ShapeTable* table = baseShape->maybeTable(nogc))
table->checkAfterMovingGC();
}
}
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {

Просмотреть файл

@ -25,6 +25,7 @@
using namespace js;
using JS::AutoCheckCannotGC;
using JS::GenericNaN;
using mozilla::ArrayLength;
using mozilla::DebugOnly;
@ -139,22 +140,23 @@ js::NativeObject::checkShapeConsistency()
Shape* shape = lastProperty();
Shape* prev = nullptr;
AutoCheckCannotGC nogc;
if (inDictionaryMode()) {
MOZ_ASSERT(shape->hasTable());
if (ShapeTable* table = shape->maybeTable(nogc)) {
for (uint32_t fslot = table->freeList();
fslot != SHAPE_INVALID_SLOT;
fslot = getSlot(fslot).toPrivateUint32())
{
MOZ_ASSERT(fslot < slotSpan());
}
ShapeTable& table = shape->table();
for (uint32_t fslot = table.freeList();
fslot != SHAPE_INVALID_SLOT;
fslot = getSlot(fslot).toPrivateUint32())
{
MOZ_ASSERT(fslot < slotSpan());
}
for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
MOZ_ASSERT_IF(lastProperty() != shape, !shape->hasTable());
ShapeTable::Entry& entry = table.search<MaybeAdding::NotAdding>(shape->propid());
MOZ_ASSERT(entry.shape() == shape);
ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(),
nogc);
MOZ_ASSERT(entry.shape() == shape);
}
}
shape = lastProperty();
@ -170,11 +172,11 @@ js::NativeObject::checkShapeConsistency()
}
} else {
for (int n = throttle; --n >= 0 && shape->parent; shape = shape->parent) {
if (shape->hasTable()) {
ShapeTable& table = shape->table();
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());
ShapeTable::Entry& entry =
table->search<MaybeAdding::NotAdding>(r.front().propid(), nogc);
MOZ_ASSERT(entry.shape() == &r.front());
}
}
@ -251,8 +253,7 @@ Shape*
js::NativeObject::lookup(ExclusiveContext* cx, jsid id)
{
MOZ_ASSERT(isNative());
ShapeTable::Entry* entry;
return Shape::search(cx, lastProperty(), id, &entry);
return Shape::search(cx, lastProperty(), id);
}
Shape*
@ -522,15 +523,20 @@ NativeObject::sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj,
RootedId id(cx, INT_TO_JSID(index));
AutoKeepShapeTables keep(cx);
ShapeTable::Entry* entry = nullptr;
if (obj->inDictionaryMode())
entry = &obj->lastProperty()->table().search<MaybeAdding::Adding>(id);
if (obj->inDictionaryMode()) {
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table)
return false;
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
// NOTE: We don't use addDataProperty because we don't want the
// extensibility check if we're, for example, sparsifying frozen objects..
if (!addPropertyInternal(cx, obj, id, nullptr, nullptr, slot,
obj->getElementsHeader()->elementAttributes(),
0, entry, true)) {
0, entry, true, keep)) {
obj->setDenseElementUnchecked(index, value);
return false;
}
@ -946,26 +952,28 @@ NativeObject::allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t*
uint32_t slot = obj->slotSpan();
MOZ_ASSERT(slot >= JSSLOT_FREE(obj->getClass()));
/*
* If this object is in dictionary mode, try to pull a free slot from the
* shape table's slot-number freelist.
*/
// If this object is in dictionary mode, try to pull a free slot from the
// shape table's slot-number free list. Shapes without a ShapeTable have an
// empty free list, because we only purge ShapeTables with an empty free
// list.
if (obj->inDictionaryMode()) {
ShapeTable& table = obj->lastProperty()->table();
uint32_t last = table.freeList();
if (last != SHAPE_INVALID_SLOT) {
AutoCheckCannotGC nogc;
if (ShapeTable* table = obj->lastProperty()->maybeTable(nogc)) {
uint32_t last = table->freeList();
if (last != SHAPE_INVALID_SLOT) {
#ifdef DEBUG
MOZ_ASSERT(last < slot);
uint32_t next = obj->getSlot(last).toPrivateUint32();
MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot);
MOZ_ASSERT(last < slot);
uint32_t next = obj->getSlot(last).toPrivateUint32();
MOZ_ASSERT_IF(next != SHAPE_INVALID_SLOT, next < slot);
#endif
*slotp = last;
*slotp = last;
const Value& vref = obj->getSlot(last);
table.setFreeList(vref.toPrivateUint32());
obj->setSlot(last, UndefinedValue());
return true;
const Value& vref = obj->getSlot(last);
table->setFreeList(vref.toPrivateUint32());
obj->setSlot(last, UndefinedValue());
return true;
}
}
}
@ -983,26 +991,33 @@ NativeObject::allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t*
}
void
NativeObject::freeSlot(uint32_t slot)
NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot)
{
MOZ_ASSERT(slot < slotSpan());
if (inDictionaryMode()) {
ShapeTable& table = lastProperty()->table();
uint32_t last = table.freeList();
// Ensure we have a ShapeTable as it stores the object's free list (the
// list of available slots in dictionary objects).
AutoCheckCannotGC nogc;
if (ShapeTable* table = lastProperty()->ensureTableForDictionary(cx, nogc)) {
uint32_t last = table->freeList();
/* Can't afford to check the whole freelist, but let's check the head. */
MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
// Can't afford to check the whole free list, but let's check the head.
MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan() && last != slot);
/*
* Place all freed slots other than reserved slots (bug 595230) on the
* dictionary's free list.
*/
if (JSSLOT_FREE(getClass()) <= slot) {
MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
setSlot(slot, PrivateUint32Value(last));
table.setFreeList(slot);
return;
// Place all freed slots other than reserved slots (bug 595230) on the
// dictionary's free list.
if (JSSLOT_FREE(getClass()) <= slot) {
MOZ_ASSERT_IF(last != SHAPE_INVALID_SLOT, last < slotSpan());
setSlot(slot, PrivateUint32Value(last));
table->setFreeList(slot);
return;
}
} else {
// OOM while creating the ShapeTable holding the free list. We can
// recover from it - it just means we won't be able to reuse this
// slot later.
cx->recoverFromOutOfMemory();
}
}
setSlot(slot, UndefinedValue());

Просмотреть файл

@ -727,7 +727,7 @@ class NativeObject : public ShapedObject
* logic across the object vs. shape module wall.
*/
static bool allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t* slotp);
void freeSlot(uint32_t slot);
void freeSlot(ExclusiveContext* cx, uint32_t slot);
private:
static Shape* getChildPropertyOnDictionary(ExclusiveContext* cx, HandleNativeObject obj,
@ -782,7 +782,8 @@ class NativeObject : public ShapedObject
static Shape*
addPropertyInternal(ExclusiveContext* cx, HandleNativeObject obj, HandleId id,
JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs,
unsigned flags, ShapeTable::Entry* entry, bool allowDictionary);
unsigned flags, ShapeTable::Entry* entry, bool allowDictionary,
const AutoKeepShapeTables& keep);
bool fillInAfterSwap(JSContext* cx, const Vector<Value>& values, void* priv);

Просмотреть файл

@ -1,4 +1,3 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
@ -23,6 +22,20 @@
namespace js {
inline
AutoKeepShapeTables::AutoKeepShapeTables(ExclusiveContext* cx)
: cx_(cx),
prev_(cx->zone()->keepShapeTables())
{
cx->zone()->setKeepShapeTables(true);
}
inline
AutoKeepShapeTables::~AutoKeepShapeTables()
{
cx_->zone()->setKeepShapeTables(prev_);
}
inline
StackBaseShape::StackBaseShape(ExclusiveContext* cx, const Class* clasp, uint32_t objectFlags)
: flags(objectFlags),
@ -32,50 +45,61 @@ StackBaseShape::StackBaseShape(ExclusiveContext* cx, const Class* clasp, uint32_
inline Shape*
Shape::search(ExclusiveContext* cx, jsid id)
{
ShapeTable::Entry* _;
return search(cx, this, id, &_);
return search(cx, this, id);
}
MOZ_ALWAYS_INLINE bool
Shape::maybeCreateTableForLookup(ExclusiveContext* cx)
{
if (hasTable())
return true;
if (!inDictionary() && numLinearSearches() < LINEAR_SEARCHES_MAX) {
incrementNumLinearSearches();
return true;
}
if (!isBigEnoughForAShapeTable())
return true;
return Shape::hashify(cx, this);
}
template<MaybeAdding Adding>
/* static */ inline bool
Shape::search(ExclusiveContext* cx, Shape* start, jsid id, const AutoKeepShapeTables& keep,
Shape** pshape, ShapeTable::Entry** pentry)
{
if (start->inDictionary()) {
ShapeTable* table = start->ensureTableForDictionary(cx, keep);
if (!table)
return false;
*pentry = &table->search<Adding>(id, keep);
*pshape = (*pentry)->shape();
return true;
}
*pentry = nullptr;
*pshape = Shape::search<Adding>(cx, start, id);
return true;
}
template<MaybeAdding Adding>
/* static */ inline Shape*
Shape::search(ExclusiveContext* cx, Shape* start, jsid id, ShapeTable::Entry** pentry)
Shape::search(ExclusiveContext* cx, Shape* start, jsid id)
{
if (start->inDictionary()) {
*pentry = &start->table().search<Adding>(id);
return (*pentry)->shape();
}
*pentry = nullptr;
if (start->hasTable()) {
ShapeTable::Entry& entry = start->table().search<Adding>(id);
return entry.shape();
}
if (start->numLinearSearches() == LINEAR_SEARCHES_MAX) {
if (start->isBigEnoughForAShapeTable()) {
if (Shape::hashify(cx, start)) {
ShapeTable::Entry& entry = start->table().search<Adding>(id);
return entry.shape();
} else {
cx->recoverFromOutOfMemory();
}
if (start->maybeCreateTableForLookup(cx)) {
JS::AutoCheckCannotGC nogc;
if (ShapeTable* table = start->maybeTable(nogc)) {
ShapeTable::Entry& entry = table->search<Adding>(id, nogc);
return entry.shape();
}
/*
* No table built -- there weren't enough entries, or OOM occurred.
* Don't increment numLinearSearches, to keep hasTable() false.
*/
MOZ_ASSERT(!start->hasTable());
} else {
start->incrementNumLinearSearches();
// Just do a linear search.
cx->recoverFromOutOfMemory();
}
for (Shape* shape = start; shape; shape = shape->parent) {
if (shape->propidRef() == id)
return shape;
}
return nullptr;
return start->searchLinear(id);
}
inline Shape*

Просмотреть файл

@ -35,6 +35,8 @@ using mozilla::DebugOnly;
using mozilla::PodZero;
using mozilla::RotateLeft;
using JS::AutoCheckCannotGC;
Shape* const ShapeTable::Entry::SHAPE_REMOVED = (Shape*)ShapeTable::Entry::SHAPE_COLLISION;
bool
@ -57,7 +59,7 @@ ShapeTable::init(ExclusiveContext* cx, Shape* lastProp)
for (Shape::Range<NoGC> r(lastProp); !r.empty(); r.popFront()) {
Shape& shape = r.front();
Entry& entry = search<MaybeAdding::Adding>(shape.propid());
Entry& entry = searchUnchecked<MaybeAdding::Adding>(shape.propid());
/*
* Beware duplicate args and arg vs. var conflicts: the youngest shape
@ -185,7 +187,7 @@ Hash2(HashNumber hash0, uint32_t log2, uint32_t shift)
template<MaybeAdding Adding>
ShapeTable::Entry&
ShapeTable::search(jsid id)
ShapeTable::searchUnchecked(jsid id)
{
MOZ_ASSERT(entries_);
MOZ_ASSERT(!JSID_IS_EMPTY(id));
@ -260,8 +262,8 @@ ShapeTable::search(jsid id)
MOZ_CRASH("Shape::search failed to find an expected entry.");
}
template ShapeTable::Entry& ShapeTable::search<MaybeAdding::Adding>(jsid id);
template ShapeTable::Entry& ShapeTable::search<MaybeAdding::NotAdding>(jsid id);
template ShapeTable::Entry& ShapeTable::searchUnchecked<MaybeAdding::Adding>(jsid id);
template ShapeTable::Entry& ShapeTable::searchUnchecked<MaybeAdding::NotAdding>(jsid id);
bool
ShapeTable::change(ExclusiveContext* cx, int log2Delta)
@ -288,9 +290,10 @@ ShapeTable::change(ExclusiveContext* cx, int log2Delta)
entries_ = 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());
Entry& entry = search<MaybeAdding::Adding>(shape->propid(), nogc);
MOZ_ASSERT(entry.isFree());
entry.setShape(shape);
}
@ -533,12 +536,17 @@ NativeObject::addProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
return nullptr;
}
AutoKeepShapeTables keep(cx);
ShapeTable::Entry* entry = nullptr;
if (obj->inDictionaryMode())
entry = &obj->lastProperty()->table().search<MaybeAdding::Adding>(id);
if (obj->inDictionaryMode()) {
ShapeTable* table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table)
return nullptr;
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags, entry,
allowDictionary);
allowDictionary, keep);
}
static bool
@ -559,7 +567,7 @@ NativeObject::addPropertyInternal(ExclusiveContext* cx,
GetterOp getter, SetterOp setter,
uint32_t slot, unsigned attrs,
unsigned flags, ShapeTable::Entry* entry,
bool allowDictionary)
bool allowDictionary, const AutoKeepShapeTables& keep)
{
MOZ_ASSERT_IF(!allowDictionary, !obj->inDictionaryMode());
MOZ_ASSERT(getter != JS_PropertyStub);
@ -584,15 +592,17 @@ NativeObject::addPropertyInternal(ExclusiveContext* cx,
{
if (!obj->toDictionaryMode(cx))
return nullptr;
table = &obj->lastProperty()->table();
entry = &table->search<MaybeAdding::Adding>(id);
table = obj->lastProperty()->maybeTable(keep);
entry = &table->search<MaybeAdding::Adding>(id, keep);
}
} else {
table = &obj->lastProperty()->table();
table = obj->lastProperty()->ensureTableForDictionary(cx, keep);
if (!table)
return nullptr;
if (table->needsToGrow()) {
if (!table->grow(cx))
return nullptr;
entry = &table->search<MaybeAdding::Adding>(id);
entry = &table->search<MaybeAdding::Adding>(id, keep);
MOZ_ASSERT(!entry->shape());
}
}
@ -632,7 +642,7 @@ NativeObject::addPropertyInternal(ExclusiveContext* cx,
table->incEntryCount();
/* Pass the table along to the new last property, namely shape. */
MOZ_ASSERT(&shape->parent->table() == table);
MOZ_ASSERT(shape->parent->maybeTable(keep) == table);
shape->parent->handoffTableTo(shape);
}
@ -752,8 +762,15 @@ NativeObject::putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
* locally. Only for those objects we can try to claim an entry in its
* shape table.
*/
AutoKeepShapeTables keep(cx);
ShapeTable::Entry* entry;
RootedShape shape(cx, Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, &entry));
RootedShape shape(cx);
if (!Shape::search<MaybeAdding::Adding>(cx, obj->lastProperty(), id, keep,
shape.address(), &entry))
{
return nullptr;
}
if (!shape) {
/*
* You can't add properties to a non-extensible object, but you can change
@ -771,7 +788,7 @@ NativeObject::putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
}
return addPropertyInternal(cx, obj, id, getter, setter, slot, attrs, flags,
entry, true);
entry, true, keep);
}
/* Property exists: search must have returned a valid entry. */
@ -817,7 +834,9 @@ NativeObject::putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
if (shape != obj->lastProperty() && !obj->inDictionaryMode()) {
if (!obj->toDictionaryMode(cx))
return nullptr;
entry = &obj->lastProperty()->table().search<MaybeAdding::NotAdding>(shape->propid());
ShapeTable* table = obj->lastProperty()->maybeTable(keep);
MOZ_ASSERT(table);
entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep);
shape = entry->shape();
}
@ -901,7 +920,7 @@ NativeObject::putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId
*/
if (hadSlot && !shape->hasSlot()) {
if (oldSlot < obj->slotSpan())
obj->freeSlot(oldSlot);
obj->freeSlot(cx, oldSlot);
/* Note: The optimization based on propertyRemovals is only relevant to the main thread. */
if (cx->isJSContext())
++cx->asJSContext()->runtime()->propertyRemovals;
@ -953,8 +972,12 @@ NativeObject::removeProperty(ExclusiveContext* cx, jsid id_)
RootedId id(cx, id_);
RootedNativeObject self(cx, this);
AutoKeepShapeTables keep(cx);
ShapeTable::Entry* entry;
RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &entry));
RootedShape shape(cx);
if (!Shape::search(cx, lastProperty(), id, keep, shape.address(), &entry))
return false;
if (!shape)
return true;
@ -965,7 +988,9 @@ NativeObject::removeProperty(ExclusiveContext* cx, jsid id_)
if (!self->inDictionaryMode() && (shape != self->lastProperty() || !self->canRemoveLastProperty())) {
if (!self->toDictionaryMode(cx))
return false;
entry = &self->lastProperty()->table().search<MaybeAdding::NotAdding>(shape->propid());
ShapeTable* table = self->lastProperty()->maybeTable(keep);
MOZ_ASSERT(table);
entry = &table->search<MaybeAdding::NotAdding>(shape->propid(), keep);
shape = entry->shape();
}
@ -1001,7 +1026,7 @@ NativeObject::removeProperty(ExclusiveContext* cx, jsid id_)
/* If shape has a slot, free its slot number. */
if (shape->hasSlot()) {
self->freeSlot(shape->slot());
self->freeSlot(cx, shape->slot());
if (cx->isJSContext())
++cx->asJSContext()->runtime()->propertyRemovals;
}
@ -1012,15 +1037,16 @@ NativeObject::removeProperty(ExclusiveContext* cx, jsid id_)
* list and hash in place.
*/
if (self->inDictionaryMode()) {
ShapeTable& table = self->lastProperty()->table();
ShapeTable* table = self->lastProperty()->maybeTable(keep);
MOZ_ASSERT(table);
if (entry->hadCollision()) {
entry->setRemoved();
table.decEntryCount();
table.incRemovedCount();
table->decEntryCount();
table->incRemovedCount();
} else {
entry->setFree();
table.decEntryCount();
table->decEntryCount();
#ifdef DEBUG
/*
@ -1047,9 +1073,9 @@ NativeObject::removeProperty(ExclusiveContext* cx, jsid id_)
JS_ALWAYS_TRUE(self->generateOwnShape(cx, 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);
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
@ -1145,10 +1171,14 @@ NativeObject::replaceWithNewEquivalentShape(ExclusiveContext* cx, Shape* oldShap
oldShape = oldRoot;
}
ShapeTable& table = self->lastProperty()->table();
AutoCheckCannotGC nogc;
ShapeTable* table = self->lastProperty()->ensureTableForDictionary(cx, nogc);
if (!table)
return nullptr;
ShapeTable::Entry* entry = oldShape->isEmptyShape()
? nullptr
: &table.search<MaybeAdding::NotAdding>(oldShape->propidRef());
? nullptr
: &table->search<MaybeAdding::NotAdding>(oldShape->propidRef(), nogc);
/*
* Splice the new shape into the same position as the old shape, preserving
@ -1283,10 +1313,8 @@ BaseShape::adoptUnowned(UnownedBaseShape* other)
MOZ_ASSERT(isOwned());
uint32_t span = slotSpan();
ShapeTable* table = &this->table();
BaseShape::copyFromUnowned(*this, *other);
setTable(table);
setSlotSpan(span);
assertConsistency();
@ -1350,8 +1378,9 @@ BaseShape::traceChildrenSkipShapeTable(JSTracer* trc)
void
BaseShape::traceShapeTable(JSTracer* trc)
{
if (hasTable())
table().trace(trc);
AutoCheckCannotGC nogc;
if (ShapeTable* table = maybeTable(nogc))
table->trace(trc);
}
#ifdef DEBUG
@ -1361,18 +1390,20 @@ BaseShape::canSkipMarkingShapeTable(Shape* lastShape)
// Check that every shape in the shape table will be marked by marking
// |lastShape|.
if (!hasTable())
AutoCheckCannotGC nogc;
ShapeTable* table = maybeTable(nogc);
if (!table)
return true;
uint32_t count = 0;
for (Shape::Range<NoGC> r(lastShape); !r.empty(); r.popFront()) {
Shape* shape = &r.front();
ShapeTable::Entry& entry = table().search<MaybeAdding::NotAdding>(shape->propid());
ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(), nogc);
if (entry.isLive())
count++;
}
return count == table().entryCount();
return count == table->entryCount();
}
#endif
@ -1732,8 +1763,9 @@ JS::ubi::Concrete<js::Shape>::size(mozilla::MallocSizeOf mallocSizeOf) const
{
Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind());
if (get().hasTable())
size += get().table().sizeOfIncludingThis(mallocSizeOf);
AutoCheckCannotGC nogc;
if (ShapeTable* table = get().maybeTable(nogc))
size += table->sizeOfIncludingThis(mallocSizeOf);
if (!get().inDictionary() && get().kids.isHash())
size += get().kids.toHash()->sizeOfIncludingThis(mallocSizeOf);

Просмотреть файл

@ -93,6 +93,12 @@
* property tree Shapes never change, but shape tables for dictionary mode
* Shapes can grow and shrink.
*
* To save memory, shape tables can be discarded on GC and recreated when
* needed. AutoKeepShapeTables can be used to avoid discarding shape tables
* for a particular zone. Methods operating on ShapeTables take either an
* AutoCheckCannotGC or AutoKeepShapeTables argument, to help ensure tables
* are not purged while we're using them.
*
* There used to be a long, math-heavy comment here explaining why property
* trees are more space-efficient than alternatives. This was removed in bug
* 631138; see that bug for the full details.
@ -120,6 +126,8 @@ static const uint32_t SHAPE_MAXIMUM_SLOT = JS_BIT(24) - 2;
enum class MaybeAdding { Adding = true, NotAdding = false };
class AutoKeepShapeTables;
/*
* Shapes use multiplicative hashing, but specialized to
* minimize footprint.
@ -189,6 +197,9 @@ class ShapeTable {
Entry* entries_; /* table of ptrs to shared tree nodes */
template<MaybeAdding Adding>
Entry& searchUnchecked(jsid id);
public:
explicit ShapeTable(uint32_t nentries)
: hashShift_(HASH_BITS - MIN_SIZE_LOG2),
@ -224,7 +235,14 @@ class ShapeTable {
bool change(ExclusiveContext* cx, int log2Delta);
template<MaybeAdding Adding>
Entry& search(jsid id);
MOZ_ALWAYS_INLINE Entry& search(jsid id, const AutoKeepShapeTables&) {
return searchUnchecked<Adding>(id);
}
template<MaybeAdding Adding>
MOZ_ALWAYS_INLINE Entry& search(jsid id, const JS::AutoCheckCannotGC&) {
return searchUnchecked<Adding>(id);
}
void trace(JSTracer* trc);
#ifdef JSGC_HASH_TABLE_CHECKS
@ -266,6 +284,20 @@ class ShapeTable {
bool grow(ExclusiveContext* cx);
};
// Ensures no shape tables are purged in the current zone.
class MOZ_RAII AutoKeepShapeTables
{
ExclusiveContext* cx_;
bool prev_;
AutoKeepShapeTables(const AutoKeepShapeTables&) = delete;
void operator=(const AutoKeepShapeTables&) = delete;
public:
explicit inline AutoKeepShapeTables(ExclusiveContext* cx);
inline ~AutoKeepShapeTables();
};
/*
* Use the reserved attribute bit to mean shadowability.
*/
@ -414,9 +446,23 @@ class BaseShape : public gc::TenuredCell
uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
bool hasTable() const { MOZ_ASSERT_IF(table_, isOwned()); return table_ != nullptr; }
ShapeTable& table() const { MOZ_ASSERT(table_ && isOwned()); return *table_; }
void setTable(ShapeTable* table) { MOZ_ASSERT(isOwned()); table_ = table; }
ShapeTable* maybeTable(const AutoKeepShapeTables&) const {
MOZ_ASSERT_IF(table_, isOwned());
return table_;
}
ShapeTable* maybeTable(const JS::AutoCheckCannotGC&) const {
MOZ_ASSERT_IF(table_, isOwned());
return table_;
}
void maybePurgeTable() {
if (table_ && table_->freeList() == SHAPE_INVALID_SLOT) {
js_delete(table_);
table_ = nullptr;
}
}
uint32_t slotSpan() const { MOZ_ASSERT(isOwned()); return slotSpan_; }
void setSlotSpan(uint32_t slotSpan) { MOZ_ASSERT(isOwned()); slotSpan_ = slotSpan; }
@ -580,8 +626,13 @@ class Shape : public gc::TenuredCell
};
template<MaybeAdding Adding = MaybeAdding::NotAdding>
static inline Shape* search(ExclusiveContext* cx, Shape* start, jsid id,
ShapeTable::Entry** pentry);
static inline Shape* search(ExclusiveContext* cx, Shape* start, jsid id);
template<MaybeAdding Adding = MaybeAdding::NotAdding>
static inline MOZ_MUST_USE bool search(ExclusiveContext* cx, Shape* start, jsid id,
const AutoKeepShapeTables&,
Shape** pshape, ShapeTable::Entry** pentry);
static inline Shape* searchNoHashify(Shape* start, jsid id);
void removeFromDictionary(NativeObject* obj);
@ -618,18 +669,39 @@ class Shape : public gc::TenuredCell
bool makeOwnBaseShape(ExclusiveContext* cx);
MOZ_ALWAYS_INLINE MOZ_MUST_USE bool maybeCreateTableForLookup(ExclusiveContext* cx);
public:
bool hasTable() const { return base()->hasTable(); }
ShapeTable& table() const { return base()->table(); }
ShapeTable* maybeTable(const AutoKeepShapeTables& keep) const {
return base()->maybeTable(keep);
}
ShapeTable* maybeTable(const JS::AutoCheckCannotGC& check) const {
return base()->maybeTable(check);
}
template <typename T>
MOZ_MUST_USE ShapeTable* ensureTableForDictionary(ExclusiveContext* cx, const T& nogc) {
MOZ_ASSERT(inDictionary());
if (ShapeTable* table = maybeTable(nogc))
return table;
if (!hashify(cx, this))
return nullptr;
ShapeTable* table = maybeTable(nogc);
MOZ_ASSERT(table);
return table;
}
void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
JS::ShapeInfo* info) const
{
if (hasTable()) {
JS::AutoCheckCannotGC nogc;
if (ShapeTable* table = maybeTable(nogc)) {
if (inDictionary())
info->shapesMallocHeapDictTables += table().sizeOfIncludingThis(mallocSizeOf);
info->shapesMallocHeapDictTables += table->sizeOfIncludingThis(mallocSizeOf);
else
info->shapesMallocHeapTreeTables += table().sizeOfIncludingThis(mallocSizeOf);
info->shapesMallocHeapTreeTables += table->sizeOfIncludingThis(mallocSizeOf);
}
if (!inDictionary() && kids.isHash())
@ -892,8 +964,9 @@ class Shape : public gc::TenuredCell
bool hasShadowable() const { return attrs & JSPROP_SHADOWABLE; }
uint32_t entryCount() {
if (hasTable())
return table().entryCount();
JS::AutoCheckCannotGC nogc;
if (ShapeTable* table = maybeTable(nogc))
return table->entryCount();
uint32_t count = 0;
for (Shape::Range<NoGC> r(this); !r.empty(); r.popFront())
++count;
@ -913,7 +986,6 @@ class Shape : public gc::TenuredCell
public:
bool isBigEnoughForAShapeTable() {
MOZ_ASSERT(!inDictionary());
MOZ_ASSERT(!hasTable());
// isBigEnoughForAShapeTableSlow is pretty inefficient so we only call
@ -948,7 +1020,7 @@ class Shape : public gc::TenuredCell
void traceChildren(JSTracer* trc);
inline Shape* search(ExclusiveContext* cx, jsid id);
inline Shape* searchLinear(jsid id);
MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
void fixupAfterMovingGC();
void fixupGetterSetterForBarrier(JSTracer* trc);
@ -1416,14 +1488,6 @@ Shape::initDictionaryShape(const StackShape& child, uint32_t nfixed, GCPtrShape*
inline Shape*
Shape::searchLinear(jsid id)
{
/*
* Non-dictionary shapes can acquire a table at any point the main thread
* is operating on it, so other threads inspecting such shapes can't use
* their table without racing. This function can be called from any thread
* on any non-dictionary shape.
*/
MOZ_ASSERT(!inDictionary());
for (Shape* shape = this; shape; ) {
if (shape->propidRef() == id)
return shape;
@ -1444,8 +1508,9 @@ Shape::searchNoHashify(Shape* start, jsid id)
* If we have a table, search in the shape table, else do a linear
* search. We never hashify into a table in parallel.
*/
if (start->hasTable()) {
ShapeTable::Entry& entry = start->table().search<MaybeAdding::NotAdding>(id);
JS::AutoCheckCannotGC nogc;
if (ShapeTable* table = start->maybeTable(nogc)) {
ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(id, nogc);
return entry.shape();
}