зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1314569 - Purge ShapeTables on shrinking GCs. r=jonco
This commit is contained in:
Родитель
a14435c813
Коммит
af4e54557a
|
@ -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,6 +3861,7 @@ 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);
|
||||
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
|
||||
RelazifyFunctions(zone, AllocKind::FUNCTION);
|
||||
|
@ -3868,6 +3869,16 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
|||
}
|
||||
}
|
||||
|
||||
/* Purge ShapeTables. */
|
||||
gcstats::AutoPhase ap(stats, gcstats::PHASE_PURGE_SHAPE_TABLES);
|
||||
for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
|
||||
if (zone->keepShapeTables())
|
||||
continue;
|
||||
for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next())
|
||||
baseShape->maybePurgeTable();
|
||||
}
|
||||
}
|
||||
|
||||
startNumber = number;
|
||||
|
||||
/*
|
||||
|
@ -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,11 +140,10 @@ js::NativeObject::checkShapeConsistency()
|
|||
Shape* shape = lastProperty();
|
||||
Shape* prev = nullptr;
|
||||
|
||||
AutoCheckCannotGC nogc;
|
||||
if (inDictionaryMode()) {
|
||||
MOZ_ASSERT(shape->hasTable());
|
||||
|
||||
ShapeTable& table = shape->table();
|
||||
for (uint32_t fslot = table.freeList();
|
||||
if (ShapeTable* table = shape->maybeTable(nogc)) {
|
||||
for (uint32_t fslot = table->freeList();
|
||||
fslot != SHAPE_INVALID_SLOT;
|
||||
fslot = getSlot(fslot).toPrivateUint32())
|
||||
{
|
||||
|
@ -153,9 +153,11 @@ js::NativeObject::checkShapeConsistency()
|
|||
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());
|
||||
ShapeTable::Entry& entry = table->search<MaybeAdding::NotAdding>(shape->propid(),
|
||||
nogc);
|
||||
MOZ_ASSERT(entry.shape() == shape);
|
||||
}
|
||||
}
|
||||
|
||||
shape = lastProperty();
|
||||
for (int n = throttle; --n >= 0 && shape; shape = shape->parent) {
|
||||
|
@ -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,13 +952,14 @@ 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();
|
||||
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);
|
||||
|
@ -963,11 +970,12 @@ NativeObject::allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t*
|
|||
*slotp = last;
|
||||
|
||||
const Value& vref = obj->getSlot(last);
|
||||
table.setFreeList(vref.toPrivateUint32());
|
||||
table->setFreeList(vref.toPrivateUint32());
|
||||
obj->setSlot(last, UndefinedValue());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (slot >= SHAPE_MAXIMUM_SLOT) {
|
||||
ReportOutOfMemory(cx);
|
||||
|
@ -983,27 +991,34 @@ 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. */
|
||||
// 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.
|
||||
*/
|
||||
// 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);
|
||||
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);
|
||||
if (start->maybeCreateTableForLookup(cx)) {
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
if (ShapeTable* table = start->maybeTable(nogc)) {
|
||||
ShapeTable::Entry& entry = table->search<Adding>(id, nogc);
|
||||
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 {
|
||||
// Just do a linear search.
|
||||
cx->recoverFromOutOfMemory();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
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());
|
||||
: &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();
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче