mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1016 lines
33 KiB
C++
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 */
|
|
}
|