gecko/js/src/jspropertytree.cpp

1016 lines
33 KiB
C++

/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
/* vim: set ts=40 sw=4 et tw=99: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Mozilla SpiderMonkey property tree implementation
*
* The Initial Developer of the Original Code is
* Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2002-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Brendan Eich <brendan@mozilla.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "jstypes.h"
#include "jsarena.h"
#include "jsdhash.h"
#include "jsprf.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "jsgc.h"
#include "jspropertytree.h"
#include "jsscope.h"
#include "jsnum.h"
#include "jsscopeinlines.h"
using namespace js;
struct PropertyRootKey
{
const JSScopeProperty *firstProp;
uint32 emptyShape;
PropertyRootKey(const JSScopeProperty *child, uint32 shape)
: firstProp(child), emptyShape(shape) {}
static JSDHashNumber hash(JSDHashTable *table, const void *key) {
const PropertyRootKey *rkey = (const PropertyRootKey *)key;
return rkey->firstProp->hash() ^ rkey->emptyShape;
}
};
struct PropertyRootEntry : public JSDHashEntryHdr
{
JSScopeProperty *firstProp;
uint32 emptyShape;
uint32 newEmptyShape;
static JSBool match(JSDHashTable *table, const JSDHashEntryHdr *hdr, const void *key) {
const PropertyRootEntry *rent = (const PropertyRootEntry *)hdr;
const PropertyRootKey *rkey = (const PropertyRootKey *)key;
return rent->firstProp->matches(rkey->firstProp) &&
rent->emptyShape == rkey->emptyShape;
}
};
static const JSDHashTableOps PropertyRootHashOps = {
JS_DHashAllocTable,
JS_DHashFreeTable,
PropertyRootKey::hash,
PropertyRootEntry::match,
JS_DHashMoveEntryStub,
JS_DHashClearEntryStub,
JS_DHashFinalizeStub,
NULL
};
static JSDHashNumber
HashScopeProperty(JSDHashTable *table, const void *key)
{
const JSScopeProperty *sprop = (const JSScopeProperty *)key;
return sprop->hash();
}
static JSBool
MatchScopeProperty(JSDHashTable *table,
const JSDHashEntryHdr *hdr,
const void *key)
{
const JSPropertyTreeEntry *entry = (const JSPropertyTreeEntry *)hdr;
const JSScopeProperty *sprop = entry->child;
const JSScopeProperty *kprop = (const JSScopeProperty *)key;
return sprop->matches(kprop);
}
static const JSDHashTableOps PropertyTreeHashOps = {
JS_DHashAllocTable,
JS_DHashFreeTable,
HashScopeProperty,
MatchScopeProperty,
JS_DHashMoveEntryStub,
JS_DHashClearEntryStub,
JS_DHashFinalizeStub,
NULL
};
bool
PropertyTree::init()
{
if (!JS_DHashTableInit(&hash, &PropertyRootHashOps, NULL,
sizeof(PropertyRootEntry), JS_DHASH_MIN_SIZE)) {
hash.ops = NULL;
return false;
}
JS_InitArenaPool(&arenaPool, "properties",
256 * sizeof(JSScopeProperty), sizeof(void *), NULL);
emptyShapeChanges = 0;
return true;
}
void
PropertyTree::finish()
{
if (hash.ops) {
JS_DHashTableFinish(&hash);
hash.ops = NULL;
}
JS_FinishArenaPool(&arenaPool);
}
/*
* NB: Called with cx->runtime->gcLock held if gcLocked is true.
* On failure, return null after unlocking the GC and reporting out of memory.
*/
JSScopeProperty *
PropertyTree::newScopeProperty(JSContext *cx, bool gcLocked)
{
JSScopeProperty *sprop;
if (!gcLocked)
JS_LOCK_GC(cx->runtime);
sprop = freeList;
if (sprop) {
sprop->removeFree();
} else {
JS_ARENA_ALLOCATE_CAST(sprop, JSScopeProperty *, &arenaPool,
sizeof(JSScopeProperty));
if (!sprop) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx);
return NULL;
}
}
if (!gcLocked)
JS_UNLOCK_GC(cx->runtime);
JS_RUNTIME_METER(cx->runtime, livePropTreeNodes);
JS_RUNTIME_METER(cx->runtime, totalPropTreeNodes);
return sprop;
}
#define CHUNKY_KIDS_TAG ((jsuword)1)
#define KIDS_IS_CHUNKY(kids) ((jsuword)(kids) & CHUNKY_KIDS_TAG)
#define KIDS_TO_CHUNK(kids) ((PropTreeKidsChunk *) \
((jsuword)(kids) & ~CHUNKY_KIDS_TAG))
#define CHUNK_TO_KIDS(chunk) ((JSScopeProperty *) \
((jsuword)(chunk) | CHUNKY_KIDS_TAG))
#define MAX_KIDS_PER_CHUNK 10
#define CHUNK_HASH_THRESHOLD 30
struct PropTreeKidsChunk {
JSScopeProperty *kids[MAX_KIDS_PER_CHUNK];
JSDHashTable *table;
PropTreeKidsChunk *next;
};
/*
* NB: Called with cx->runtime->gcLock held, always.
* On failure, return null after unlocking the GC and reporting out of memory.
*/
static PropTreeKidsChunk *
NewPropTreeKidsChunk(JSContext *cx)
{
PropTreeKidsChunk *chunk;
chunk = (PropTreeKidsChunk *) js_calloc(sizeof *chunk);
if (!chunk) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx);
return NULL;
}
JS_ASSERT(((jsuword)chunk & CHUNKY_KIDS_TAG) == 0);
JS_RUNTIME_METER(cx->runtime, propTreeKidsChunks);
return chunk;
}
static PropTreeKidsChunk *
DestroyPropTreeKidsChunk(JSContext *cx, PropTreeKidsChunk *chunk)
{
JS_RUNTIME_UNMETER(cx->runtime, propTreeKidsChunks);
if (chunk->table)
JS_DHashTableDestroy(chunk->table);
PropTreeKidsChunk *nextChunk = chunk->next;
js_free(chunk);
return nextChunk;
}
/*
* NB: Called with cx->runtime->gcLock held, always.
* On failure, return null after unlocking the GC and reporting out of memory.
*/
bool
PropertyTree::insertChild(JSContext *cx, JSScopeProperty *parent,
JSScopeProperty *child)
{
JS_ASSERT(parent);
JS_ASSERT(!child->parent);
JS_ASSERT(!JSVAL_IS_NULL(parent->id));
JS_ASSERT(!JSVAL_IS_NULL(child->id));
JSScopeProperty **childp = &parent->kids;
if (JSScopeProperty *kids = *childp) {
JSScopeProperty *sprop;
PropTreeKidsChunk *chunk;
if (!KIDS_IS_CHUNKY(kids)) {
sprop = kids;
JS_ASSERT(sprop != child);
if (sprop->matches(child)) {
/*
* Duplicate child created while racing to getChild on the same
* node label. See PropertyTree::getChild, further below.
*/
JS_RUNTIME_METER(cx->runtime, duplicatePropTreeNodes);
}
chunk = NewPropTreeKidsChunk(cx);
if (!chunk)
return false;
parent->kids = CHUNK_TO_KIDS(chunk);
chunk->kids[0] = sprop;
childp = &chunk->kids[1];
} else {
PropTreeKidsChunk **chunkp;
chunk = KIDS_TO_CHUNK(kids);
if (JSDHashTable *table = chunk->table) {
JSPropertyTreeEntry *entry = (JSPropertyTreeEntry *)
JS_DHashTableOperate(table, child, JS_DHASH_ADD);
if (!entry) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx);
return false;
}
if (!entry->child) {
entry->child = child;
while (chunk->next)
chunk = chunk->next;
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
childp = &chunk->kids[i];
sprop = *childp;
if (!sprop)
goto insert;
}
chunkp = &chunk->next;
goto new_chunk;
}
}
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
childp = &chunk->kids[i];
sprop = *childp;
if (!sprop)
goto insert;
JS_ASSERT(sprop != child);
if (sprop->matches(child)) {
/*
* Duplicate child, see comment above. In this
* case, we must let the duplicate be inserted at
* this level in the tree, so we keep iterating,
* looking for an empty slot in which to insert.
*/
JS_ASSERT(sprop != child);
JS_RUNTIME_METER(cx->runtime, duplicatePropTreeNodes);
}
}
chunkp = &chunk->next;
} while ((chunk = *chunkp) != NULL);
new_chunk:
chunk = NewPropTreeKidsChunk(cx);
if (!chunk)
return false;
*chunkp = chunk;
childp = &chunk->kids[0];
}
}
insert:
*childp = child;
child->parent = parent;
return true;
}
/* NB: Called with cx->runtime->gcLock held. */
void
PropertyTree::removeChild(JSContext *cx, JSScopeProperty *child)
{
uintN i, j;
JSPropertyTreeEntry *entry;
JSScopeProperty *parent = child->parent;
JS_ASSERT(parent);
JS_ASSERT(!JSVAL_IS_NULL(parent->id));
JSScopeProperty *kids = parent->kids;
if (!KIDS_IS_CHUNKY(kids)) {
JSScopeProperty *kid = kids;
if (kid == child)
parent->kids = NULL;
return;
}
PropTreeKidsChunk *list = KIDS_TO_CHUNK(kids);
PropTreeKidsChunk *chunk = list;
PropTreeKidsChunk **chunkp = &list;
JSDHashTable *table = chunk->table;
PropTreeKidsChunk *freeChunk = NULL;
do {
for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
if (chunk->kids[i] == child) {
PropTreeKidsChunk *lastChunk = chunk;
if (!lastChunk->next) {
j = i + 1;
} else {
j = 0;
do {
chunkp = &lastChunk->next;
lastChunk = *chunkp;
} while (lastChunk->next);
}
for (; j < MAX_KIDS_PER_CHUNK; j++) {
if (!lastChunk->kids[j])
break;
}
--j;
if (chunk != lastChunk || j > i)
chunk->kids[i] = lastChunk->kids[j];
lastChunk->kids[j] = NULL;
if (j == 0) {
*chunkp = NULL;
if (!list)
parent->kids = NULL;
freeChunk = lastChunk;
}
goto out;
}
}
chunkp = &chunk->next;
} while ((chunk = *chunkp) != NULL);
out:
if (table) {
entry = (JSPropertyTreeEntry *)
JS_DHashTableOperate(table, child, JS_DHASH_LOOKUP);
if (entry->child == child)
JS_DHashTableRawRemove(table, &entry->hdr);
}
if (freeChunk)
DestroyPropTreeKidsChunk(cx, freeChunk);
}
void
PropertyTree::emptyShapeChange(uint32 oldEmptyShape, uint32 newEmptyShape)
{
if (oldEmptyShape == newEmptyShape)
return;
PropertyRootEntry *rent = (PropertyRootEntry *) hash.entryStore;
PropertyRootEntry *rend = rent + JS_DHASH_TABLE_SIZE(&hash);
while (rent < rend) {
if (rent->emptyShape == oldEmptyShape)
rent->newEmptyShape = newEmptyShape;
rent++;
}
++emptyShapeChanges;
}
static JSDHashTable *
HashChunks(PropTreeKidsChunk *chunk, uintN n)
{
JSDHashTable *table;
uintN i;
JSScopeProperty *sprop;
JSPropertyTreeEntry *entry;
table = JS_NewDHashTable(&PropertyTreeHashOps, NULL,
sizeof(JSPropertyTreeEntry),
JS_DHASH_DEFAULT_CAPACITY(n + 1));
if (!table)
return NULL;
do {
for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
sprop = chunk->kids[i];
if (!sprop)
break;
entry = (JSPropertyTreeEntry *)
JS_DHashTableOperate(table, sprop, JS_DHASH_ADD);
entry->child = sprop;
}
} while ((chunk = chunk->next) != NULL);
return table;
}
/*
* Called without cx->runtime->gcLock held. This function acquires that lock
* only when inserting a new child. Thus there may be races to find or add a
* node that result in duplicates. We expect such races to be rare!
*
* We use cx->runtime->gcLock, not ...->rtLock, to avoid nesting the former
* inside the latter in js_GenerateShape below.
*/
JSScopeProperty *
PropertyTree::getChild(JSContext *cx, JSScopeProperty *parent, uint32 shape,
const JSScopeProperty &child)
{
PropertyRootEntry *rent;
JSScopeProperty *sprop;
if (!parent) {
PropertyRootKey rkey(&child, shape);
JS_LOCK_GC(cx->runtime);
rent = (PropertyRootEntry *) JS_DHashTableOperate(&hash, &rkey, JS_DHASH_ADD);
if (!rent) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx);
return NULL;
}
sprop = rent->firstProp;
if (sprop)
goto out;
} else {
JS_ASSERT(!JSVAL_IS_NULL(parent->id));
/*
* Because chunks are appended at the end and never deleted except by
* the GC, we can search without taking the runtime's GC lock. We may
* miss a matching sprop added by another thread, and make a duplicate
* one, but that is an unlikely, therefore small, cost. The property
* tree has extremely low fan-out below its root in popular embeddings
* with real-world workloads.
*
* Patterns such as defining closures that capture a constructor's
* environment as getters or setters on the new object that is passed
* in as |this| can significantly increase fan-out below the property
* tree root -- see bug 335700 for details.
*/
rent = NULL;
sprop = parent->kids;
if (sprop) {
if (!KIDS_IS_CHUNKY(sprop)) {
if (sprop->matches(&child))
return sprop;
} else {
PropTreeKidsChunk *chunk = KIDS_TO_CHUNK(sprop);
if (JSDHashTable *table = chunk->table) {
JS_LOCK_GC(cx->runtime);
JSPropertyTreeEntry *entry = (JSPropertyTreeEntry *)
JS_DHashTableOperate(table, &child, JS_DHASH_LOOKUP);
sprop = entry->child;
if (sprop)
goto out;
goto locked_not_found;
}
uintN n = 0;
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
sprop = chunk->kids[i];
if (!sprop) {
n += i;
if (n >= CHUNK_HASH_THRESHOLD) {
chunk = KIDS_TO_CHUNK(parent->kids);
if (!chunk->table) {
JSDHashTable *table = HashChunks(chunk, n);
if (!table) {
JS_ReportOutOfMemory(cx);
return NULL;
}
JS_LOCK_GC(cx->runtime);
if (chunk->table)
JS_DHashTableDestroy(table);
else
chunk->table = table;
goto locked_not_found;
}
}
goto not_found;
}
if (sprop->matches(&child))
return sprop;
}
n += MAX_KIDS_PER_CHUNK;
} while ((chunk = chunk->next) != NULL);
}
}
not_found:
JS_LOCK_GC(cx->runtime);
}
locked_not_found:
sprop = newScopeProperty(cx, true);
if (!sprop)
return NULL;
new (sprop) JSScopeProperty(child.id, child.rawGetter, child.rawSetter, child.slot,
child.attrs, child.flags, child.shortid);
sprop->parent = sprop->kids = NULL;
sprop->shape = js_GenerateShape(cx, true);
if (!parent) {
rent->firstProp = sprop;
rent->emptyShape = shape;
rent->newEmptyShape = 0;
} else {
if (!PropertyTree::insertChild(cx, parent, sprop))
return NULL;
}
out:
JS_UNLOCK_GC(cx->runtime);
return sprop;
}
#ifdef DEBUG
static void
MeterKidCount(JSBasicStats *bs, uintN nkids)
{
JS_BASIC_STATS_ACCUM(bs, nkids);
bs->hist[JS_MIN(nkids, 10)]++;
}
static void
MeterPropertyTree(JSBasicStats *bs, JSScopeProperty *node)
{
uintN i, nkids;
JSScopeProperty *kids, *kid;
PropTreeKidsChunk *chunk;
nkids = 0;
kids = node->kids;
if (kids) {
if (KIDS_IS_CHUNKY(kids)) {
for (chunk = KIDS_TO_CHUNK(kids); chunk; chunk = chunk->next) {
for (i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
kid = chunk->kids[i];
if (!kid)
break;
MeterPropertyTree(bs, kid);
nkids++;
}
}
} else {
MeterPropertyTree(bs, kids);
nkids = 1;
}
}
MeterKidCount(bs, nkids);
}
static JSDHashOperator
js_MeterPropertyTree(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number,
void *arg)
{
PropertyRootEntry *rent = (PropertyRootEntry *)hdr;
JSBasicStats *bs = (JSBasicStats *)arg;
MeterPropertyTree(bs, rent->firstProp);
return JS_DHASH_NEXT;
}
void
JSScopeProperty::dump(JSContext *cx, FILE *fp)
{
JS_ASSERT(!JSVAL_IS_NULL(id));
jsval idval = ID_TO_VALUE(id);
if (JSVAL_IS_INT(idval)) {
fprintf(fp, "[%ld]", (long) JSVAL_TO_INT(idval));
} else {
JSString *str;
if (JSVAL_IS_STRING(idval)) {
str = JSVAL_TO_STRING(idval);
} else {
JS_ASSERT(JSVAL_IS_OBJECT(idval));
str = js_ValueToString(cx, idval);
fputs("object ", fp);
}
if (!str)
fputs("<error>", fp);
else
js_FileEscapedString(fp, str, '"');
}
fprintf(fp, " g/s %p/%p slot %u attrs %x ",
JS_FUNC_TO_DATA_PTR(void *, rawGetter),
JS_FUNC_TO_DATA_PTR(void *, rawSetter),
slot, attrs);
if (attrs) {
int first = 1;
fputs("(", fp);
#define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(" " #display + first, fp), first = 0
DUMP_ATTR(ENUMERATE, enumerate);
DUMP_ATTR(READONLY, readonly);
DUMP_ATTR(PERMANENT, permanent);
DUMP_ATTR(GETTER, getter);
DUMP_ATTR(SETTER, setter);
DUMP_ATTR(SHARED, shared);
#undef DUMP_ATTR
fputs(") ", fp);
}
fprintf(fp, "flags %x ", flags);
if (flags) {
int first = 1;
fputs("(", fp);
#define DUMP_FLAG(name, display) if (flags & name) fputs(" " #display + first, fp), first = 0
DUMP_FLAG(ALIAS, alias);
DUMP_FLAG(HAS_SHORTID, has_shortid);
DUMP_FLAG(METHOD, method);
DUMP_FLAG(MARK, mark);
DUMP_FLAG(SHAPE_REGEN, shape_regen);
DUMP_FLAG(IN_DICTIONARY, in_dictionary);
#undef DUMP_FLAG
fputs(") ", fp);
}
fprintf(fp, "shortid %d\n", shortid);
}
void
JSScopeProperty::dumpSubtree(JSContext *cx, int level, FILE *fp)
{
fprintf(fp, "%*sid ", level, "");
dump(cx, fp);
if (kids) {
++level;
if (KIDS_IS_CHUNKY(kids)) {
PropTreeKidsChunk *chunk = KIDS_TO_CHUNK(kids);
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
JSScopeProperty *kid = chunk->kids[i];
if (!kid)
break;
JS_ASSERT(kid->parent == this);
kid->dumpSubtree(cx, level, fp);
}
} while ((chunk = chunk->next) != NULL);
} else {
JSScopeProperty *kid = kids;
JS_ASSERT(kid->parent == this);
kid->dumpSubtree(cx, level, fp);
}
}
}
#endif /* DEBUG */
static void
OrphanNodeKids(JSContext *cx, JSScopeProperty *sprop)
{
JSScopeProperty *kids = sprop->kids;
JS_ASSERT(kids);
sprop->kids = NULL;
/*
* The grandparent must have either no kids or (still, after the
* removeChild call above) chunky kids.
*/
JS_ASSERT(!sprop->parent || !sprop->parent->kids ||
KIDS_IS_CHUNKY(sprop->parent->kids));
if (KIDS_IS_CHUNKY(kids)) {
PropTreeKidsChunk *chunk = KIDS_TO_CHUNK(kids);
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
JSScopeProperty *kid = chunk->kids[i];
if (!kid)
break;
if (!JSVAL_IS_NULL(kid->id)) {
JS_ASSERT(kid->parent == sprop);
kid->parent = NULL;
}
}
} while ((chunk = DestroyPropTreeKidsChunk(cx, chunk)) != NULL);
} else {
JSScopeProperty *kid = kids;
if (!JSVAL_IS_NULL(kid->id)) {
JS_ASSERT(kid->parent == sprop);
kid->parent = NULL;
}
}
}
JSDHashOperator
js::RemoveNodeIfDead(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, void *arg)
{
PropertyRootEntry *rent = (PropertyRootEntry *)hdr;
JSScopeProperty *sprop = rent->firstProp;
JS_ASSERT(!sprop->parent);
if (!sprop->marked()) {
if (sprop->kids)
OrphanNodeKids((JSContext *)arg, sprop);
return JS_DHASH_REMOVE;
}
return JS_DHASH_NEXT;
}
void
js::SweepScopeProperties(JSContext *cx)
{
#ifdef DEBUG
JSBasicStats bs;
uint32 livePropCapacity = 0, totalLiveCount = 0;
static FILE *logfp;
if (!logfp) {
if (const char *filename = getenv("JS_PROPTREE_STATFILE"))
logfp = fopen(filename, "w");
}
if (logfp) {
JS_BASIC_STATS_INIT(&bs);
MeterKidCount(&bs, JS_PROPERTY_TREE(cx).hash.entryCount);
JS_DHashTableEnumerate(&JS_PROPERTY_TREE(cx).hash, js_MeterPropertyTree, &bs);
double props, nodes, mean, sigma;
props = cx->runtime->liveScopePropsPreSweep;
nodes = cx->runtime->livePropTreeNodes;
JS_ASSERT(nodes == bs.sum);
mean = JS_MeanAndStdDevBS(&bs, &sigma);
fprintf(logfp,
"props %g nodes %g beta %g meankids %g sigma %g max %u\n",
props, nodes, nodes / props, mean, sigma, bs.max);
JS_DumpHistogram(&bs, logfp);
}
#endif
/*
* First, remove unmarked nodes from JS_PROPERTY_TREE(cx).hash. This table
* requires special handling up front, rather than removal during regular
* heap sweeping, because we cannot find an entry in it from the firstProp
* node pointer alone -- we would need the emptyShape too.
*
* Rather than encode emptyShape in firstProp somehow (a tagged overlay on
* parent, perhaps, but that would slow down JSScope::search and other hot
* paths), we simply orphan kids of garbage nodes in the property tree's
* root-ply before sweeping the node heap.
*/
JS_DHashTableEnumerate(&JS_PROPERTY_TREE(cx).hash, RemoveNodeIfDead, cx);
/*
* Second, if any empty scopes have been reshaped, rehash the root ply of
* this tree using the new empty shape numbers as key-halves. If we run out
* of memory trying to allocate the new hash, disable the property cache by
* setting SHAPE_OVERFLOW_BIT in rt->shapeGen. The next GC will therefore
* renumber shapes as well as (we hope, eventually) free sufficient memory
* for a successful re-run through this code.
*/
if (JS_PROPERTY_TREE(cx).emptyShapeChanges) {
JSDHashTable &oldHash = JS_PROPERTY_TREE(cx).hash;
uint32 tableSize = JS_DHASH_TABLE_SIZE(&oldHash);
JSDHashTable newHash;
if (!JS_DHashTableInit(&newHash, &PropertyRootHashOps, NULL,
sizeof(PropertyRootEntry), tableSize)) {
cx->runtime->shapeGen |= SHAPE_OVERFLOW_BIT;
} else {
PropertyRootEntry *rent = (PropertyRootEntry *) oldHash.entryStore;
PropertyRootEntry *rend = rent + tableSize;
while (rent < rend) {
if (rent->firstProp) {
uint32 emptyShape = rent->newEmptyShape;
if (emptyShape == 0)
emptyShape = rent->emptyShape;
PropertyRootKey rkey(rent->firstProp, emptyShape);
PropertyRootEntry *newRent =
(PropertyRootEntry *) JS_DHashTableOperate(&newHash, &rkey, JS_DHASH_ADD);
newRent->firstProp = rent->firstProp;
newRent->emptyShape = emptyShape;
newRent->newEmptyShape = 0;
}
rent++;
}
JS_ASSERT(newHash.generation == 0);
JS_DHashTableFinish(&oldHash);
JS_PROPERTY_TREE(cx).hash = newHash;
JS_PROPERTY_TREE(cx).emptyShapeChanges = 0;
}
}
/*
* Third, sweep the heap clean of all unmarked nodes. Here we will find
* nodes already GC'ed from the root ply, but we will avoid re-orphaning
* their kids, because the kids member will already be null.
*/
JSArena **ap = &JS_PROPERTY_TREE(cx).arenaPool.first.next;
while (JSArena *a = *ap) {
JSScopeProperty *limit = (JSScopeProperty *) a->avail;
uintN liveCount = 0;
for (JSScopeProperty *sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++) {
/* If the id is null, sprop is already on the freelist. */
if (JSVAL_IS_NULL(sprop->id))
continue;
/*
* If the mark bit is set, sprop is alive, so clear the mark bit
* and continue the while loop.
*
* Regenerate sprop->shape if it hasn't already been refreshed
* during the mark phase, when live scopes' lastProp members are
* followed to update both scope->shape and lastProp->shape.
*/
if (sprop->marked()) {
sprop->clearMark();
if (cx->runtime->gcRegenShapes) {
if (sprop->hasRegenFlag())
sprop->clearRegenFlag();
else
sprop->shape = js_RegenerateShapeForGC(cx);
}
liveCount++;
continue;
}
if (!sprop->inDictionary()) {
/*
* Here, sprop is garbage to collect, but its parent might not
* be, so we may have to remove it from its parent's kids chunk
* list or kid singleton pointer set.
*
* Without a separate mark-clearing pass, we can't tell whether
* sprop->parent is live at this point, so we must remove sprop
* if its parent member is non-null. A saving grace: if sprop's
* parent is dead and swept by this point, sprop->parent will
* be null -- in the next paragraph, we null all of a property
* tree node's kids' parent links when sweeping that node.
*/
if (sprop->parent)
JS_PROPERTY_TREE(cx).removeChild(cx, sprop);
if (sprop->kids)
OrphanNodeKids(cx, sprop);
}
/*
* Note that JSScopeProperty::insertFree nulls sprop->id so we know
* that sprop is on the freelist.
*/
sprop->insertFree(JS_PROPERTY_TREE(cx).freeList);
JS_RUNTIME_UNMETER(cx->runtime, livePropTreeNodes);
}
/* If a contains no live properties, return it to the malloc heap. */
if (liveCount == 0) {
for (JSScopeProperty *sprop = (JSScopeProperty *) a->base; sprop < limit; sprop++)
sprop->removeFree();
JS_ARENA_DESTROY(&JS_PROPERTY_TREE(cx).arenaPool, a, ap);
} else {
#ifdef DEBUG
livePropCapacity += limit - (JSScopeProperty *) a->base;
totalLiveCount += liveCount;
#endif
ap = &a->next;
}
}
#ifdef DEBUG
if (logfp) {
fprintf(logfp,
"\nProperty tree stats for gcNumber %lu\n",
(unsigned long) cx->runtime->gcNumber);
fprintf(logfp, "arenautil %g%%\n",
(totalLiveCount && livePropCapacity)
? (totalLiveCount * 100.0) / livePropCapacity
: 0.0);
#define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
fprintf(logfp,
"Scope search stats:\n"
" searches: %6u\n"
" hits: %6u %5.2f%% of searches\n"
" misses: %6u %5.2f%%\n"
" hashes: %6u %5.2f%%\n"
" steps: %6u %5.2f%% %5.2f%% of hashes\n"
" stepHits: %6u %5.2f%% %5.2f%%\n"
" stepMisses: %6u %5.2f%% %5.2f%%\n"
" tableAllocFails %6u\n"
" toDictFails %6u\n"
" wrapWatchFails %6u\n"
" adds: %6u\n"
" addFails: %6u\n"
" puts: %6u\n"
" redundantPuts: %6u\n"
" putFails: %6u\n"
" changes: %6u\n"
" changeFails: %6u\n"
" compresses: %6u\n"
" grows: %6u\n"
" removes: %6u\n"
" removeFrees: %6u\n"
" uselessRemoves: %6u\n"
" shrinks: %6u\n",
js_scope_stats.searches,
js_scope_stats.hits, RATE(hits, searches),
js_scope_stats.misses, RATE(misses, searches),
js_scope_stats.hashes, RATE(hashes, searches),
js_scope_stats.steps, RATE(steps, searches), RATE(steps, hashes),
js_scope_stats.stepHits,
RATE(stepHits, searches), RATE(stepHits, hashes),
js_scope_stats.stepMisses,
RATE(stepMisses, searches), RATE(stepMisses, hashes),
js_scope_stats.tableAllocFails,
js_scope_stats.toDictFails,
js_scope_stats.wrapWatchFails,
js_scope_stats.adds,
js_scope_stats.addFails,
js_scope_stats.puts,
js_scope_stats.redundantPuts,
js_scope_stats.putFails,
js_scope_stats.changes,
js_scope_stats.changeFails,
js_scope_stats.compresses,
js_scope_stats.grows,
js_scope_stats.removes,
js_scope_stats.removeFrees,
js_scope_stats.uselessRemoves,
js_scope_stats.shrinks);
#undef RATE
fflush(logfp);
}
if (const char *filename = getenv("JS_PROPTREE_DUMPFILE")) {
char pathname[1024];
JS_snprintf(pathname, sizeof pathname, "%s.%lu",
filename, (unsigned long)cx->runtime->gcNumber);
FILE *dumpfp = fopen(pathname, "w");
if (dumpfp) {
PropertyRootEntry *rent = (PropertyRootEntry *) JS_PROPERTY_TREE(cx).hash.entryStore;
PropertyRootEntry *rend = rent + JS_DHASH_TABLE_SIZE(&JS_PROPERTY_TREE(cx).hash);
while (rent < rend) {
if (rent->firstProp) {
fprintf(dumpfp, "emptyShape %u ", rent->emptyShape);
rent->firstProp->dumpSubtree(cx, 0, dumpfp);
}
rent++;
}
fclose(dumpfp);
}
}
#endif /* DEBUG */
}