Bug 609104 - Move the property tree to the compartment (r=brendan)

This commit is contained in:
Bill McCloskey 2011-01-27 15:46:39 -08:00
Родитель a5c52e0162
Коммит fe90be1b78
17 изменённых файлов: 430 добавлений и 652 удалений

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

@ -648,10 +648,6 @@ JSRuntime::init(uint32 maxbytes)
} }
propTreeStatFilename = getenv("JS_PROPTREE_STATFILE"); propTreeStatFilename = getenv("JS_PROPTREE_STATFILE");
propTreeDumpFilename = getenv("JS_PROPTREE_DUMPFILE"); propTreeDumpFilename = getenv("JS_PROPTREE_DUMPFILE");
if (meterEmptyShapes()) {
if (!emptyShapes.init())
return false;
}
#endif #endif
if (!(atomsCompartment = js_new<JSCompartment>(this)) || if (!(atomsCompartment = js_new<JSCompartment>(this)) ||
@ -682,7 +678,7 @@ JSRuntime::init(uint32 maxbytes)
debugMode = JS_FALSE; debugMode = JS_FALSE;
return propertyTree.init() && js_InitThreads(this); return js_InitThreads(this);
} }
JSRuntime::~JSRuntime() JSRuntime::~JSRuntime()
@ -723,7 +719,6 @@ JSRuntime::~JSRuntime()
if (debuggerLock) if (debuggerLock)
JS_DESTROY_LOCK(debuggerLock); JS_DESTROY_LOCK(debuggerLock);
#endif #endif
propertyTree.finish();
} }
JS_PUBLIC_API(JSRuntime *) JS_PUBLIC_API(JSRuntime *)

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

@ -811,19 +811,6 @@ js_NewContext(JSRuntime *rt, size_t stackChunkSize)
ok = js_InitRuntimeScriptState(rt); ok = js_InitRuntimeScriptState(rt);
if (ok) if (ok)
ok = js_InitRuntimeNumberState(cx); ok = js_InitRuntimeNumberState(cx);
if (ok) {
/*
* Ensure that the empty scopes initialized by
* Shape::initRuntimeState get the desired special shapes.
* (The rt->state dance above guarantees that this abuse of
* rt->shapeGen is thread-safe.)
*/
uint32 shapeGen = rt->shapeGen;
rt->shapeGen = 0;
ok = Shape::initRuntimeState(cx);
if (rt->shapeGen < shapeGen)
rt->shapeGen = shapeGen;
}
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
JS_EndRequest(cx); JS_EndRequest(cx);
@ -1045,7 +1032,6 @@ js_DestroyContext(JSContext *cx, JSDestroyContextMode mode)
JS_BeginRequest(cx); JS_BeginRequest(cx);
#endif #endif
Shape::finishRuntimeState(cx);
js_FinishRuntimeNumberState(cx); js_FinishRuntimeNumberState(cx);
/* Unpin all common atoms before final GC. */ /* Unpin all common atoms before final GC. */

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

@ -1175,15 +1175,6 @@ struct JSRuntime {
/* Structured data callbacks are runtime-wide. */ /* Structured data callbacks are runtime-wide. */
const JSStructuredCloneCallbacks *structuredCloneCallbacks; const JSStructuredCloneCallbacks *structuredCloneCallbacks;
/*
* Shared scope property tree, and arena-pool for allocating its nodes.
* This really should be free of all locking overhead and allocated in
* thread-local storage, hence the JS_PROPERTY_TREE(cx) macro.
*/
js::PropertyTree propertyTree;
#define JS_PROPERTY_TREE(cx) ((cx)->runtime->propertyTree)
/* /*
* The propertyRemovals counter is incremented for every JSObject::clear, * The propertyRemovals counter is incremented for every JSObject::clear,
* and for each JSObject::remove method call that frees a slot in the given * and for each JSObject::remove method call that frees a slot in the given
@ -1240,17 +1231,6 @@ struct JSRuntime {
/* Literal table maintained by jsatom.c functions. */ /* Literal table maintained by jsatom.c functions. */
JSAtomState atomState; JSAtomState atomState;
/*
* Runtime-shared empty scopes for well-known built-in objects that lack
* class prototypes (the usual locus of an emptyShape). Mnemonic: ABCDEW
*/
js::EmptyShape *emptyArgumentsShape;
js::EmptyShape *emptyBlockShape;
js::EmptyShape *emptyCallShape;
js::EmptyShape *emptyDeclEnvShape;
js::EmptyShape *emptyEnumeratorShape;
js::EmptyShape *emptyWithShape;
/* /*
* Various metering fields are defined at the end of JSRuntime. In this * Various metering fields are defined at the end of JSRuntime. In this
* way there is no need to recompile all the code that refers to other * way there is no need to recompile all the code that refers to other
@ -1276,18 +1256,12 @@ struct JSRuntime {
jsrefcount nonInlineCalls; jsrefcount nonInlineCalls;
jsrefcount constructs; jsrefcount constructs;
/* Property metering. */
jsrefcount liveObjectProps; jsrefcount liveObjectProps;
jsrefcount liveObjectPropsPreSweep; jsrefcount liveObjectPropsPreSweep;
jsrefcount totalObjectProps;
jsrefcount livePropTreeNodes;
jsrefcount duplicatePropTreeNodes;
jsrefcount totalPropTreeNodes;
jsrefcount propTreeKidsChunks;
jsrefcount liveDictModeNodes;
/* /*
* NB: emptyShapes is init'ed iff at least one of these envars is set: * NB: emptyShapes (in JSCompartment) is init'ed iff at least one
* of these envars is set:
* *
* JS_PROPTREE_STATFILE statistics on the property tree forest * JS_PROPTREE_STATFILE statistics on the property tree forest
* JS_PROPTREE_DUMPFILE all paths in the property tree forest * JS_PROPTREE_DUMPFILE all paths in the property tree forest
@ -1297,12 +1271,6 @@ struct JSRuntime {
bool meterEmptyShapes() const { return propTreeStatFilename || propTreeDumpFilename; } bool meterEmptyShapes() const { return propTreeStatFilename || propTreeDumpFilename; }
typedef js::HashSet<js::EmptyShape *,
js::DefaultHasher<js::EmptyShape *>,
js::SystemAllocPolicy> EmptyShapeSet;
EmptyShapeSet emptyShapes;
/* String instrumentation. */ /* String instrumentation. */
jsrefcount liveStrings; jsrefcount liveStrings;
jsrefcount totalStrings; jsrefcount totalStrings;
@ -3214,19 +3182,19 @@ js_IsPropertyCacheDisabled(JSContext *cx)
} }
static JS_INLINE uint32 static JS_INLINE uint32
js_RegenerateShapeForGC(JSContext *cx) js_RegenerateShapeForGC(JSRuntime *rt)
{ {
JS_ASSERT(cx->runtime->gcRunning); JS_ASSERT(rt->gcRunning);
JS_ASSERT(cx->runtime->gcRegenShapes); JS_ASSERT(rt->gcRegenShapes);
/* /*
* Under the GC, compared with js_GenerateShape, we don't need to use * Under the GC, compared with js_GenerateShape, we don't need to use
* atomic increments but we still must make sure that after an overflow * atomic increments but we still must make sure that after an overflow
* the shape stays such. * the shape stays such.
*/ */
uint32 shape = cx->runtime->shapeGen; uint32 shape = rt->shapeGen;
shape = (shape + 1) | (shape & js::SHAPE_OVERFLOW_BIT); shape = (shape + 1) | (shape & js::SHAPE_OVERFLOW_BIT);
cx->runtime->shapeGen = shape; rt->shapeGen = shape;
return shape; return shape;
} }

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

@ -64,6 +64,7 @@ JSCompartment::JSCompartment(JSRuntime *rt)
data(NULL), data(NULL),
marked(false), marked(false),
active(false), active(false),
propertyTree(this),
debugMode(rt->debugMode), debugMode(rt->debugMode),
mathCache(NULL) mathCache(NULL)
{ {
@ -74,6 +75,9 @@ JSCompartment::JSCompartment(JSRuntime *rt)
JSCompartment::~JSCompartment() JSCompartment::~JSCompartment()
{ {
Shape::finishEmptyShapes(this);
propertyTree.finish();
#if ENABLE_YARR_JIT #if ENABLE_YARR_JIT
js_delete(regExpAllocator); js_delete(regExpAllocator);
#endif #endif
@ -107,12 +111,24 @@ JSCompartment::init()
if (!crossCompartmentWrappers.init()) if (!crossCompartmentWrappers.init())
return false; return false;
#ifdef JS_TRACER if (!propertyTree.init())
if (!InitJIT(&traceMonitor)) {
return false; return false;
#ifdef DEBUG
if (rt->meterEmptyShapes()) {
if (!emptyShapes.init())
return false;
} }
#endif #endif
if (!Shape::initEmptyShapes(this))
return false;
#ifdef JS_TRACER
if (!InitJIT(&traceMonitor))
return false;
#endif
#if ENABLE_YARR_JIT #if ENABLE_YARR_JIT
regExpAllocator = JSC::ExecutableAllocator::create(); regExpAllocator = JSC::ExecutableAllocator::create();
if (!regExpAllocator) if (!regExpAllocator)
@ -387,12 +403,29 @@ ScriptPoolDestroyed(JSContext *cx, mjit::JITScript *jit,
#endif #endif
void void
JSCompartment::mark(JSTracer *trc) JSCompartment::markCrossCompartment(JSTracer *trc)
{ {
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront())
MarkValue(trc, e.front().key, "cross-compartment wrapper"); MarkValue(trc, e.front().key, "cross-compartment wrapper");
} }
void
JSCompartment::mark(JSTracer *trc)
{
if (emptyArgumentsShape)
emptyArgumentsShape->trace(trc);
if (emptyBlockShape)
emptyBlockShape->trace(trc);
if (emptyCallShape)
emptyCallShape->trace(trc);
if (emptyDeclEnvShape)
emptyDeclEnvShape->trace(trc);
if (emptyEnumeratorShape)
emptyEnumeratorShape->trace(trc);
if (emptyWithShape)
emptyWithShape->trace(trc);
}
void void
JSCompartment::sweep(JSContext *cx, uint32 releaseInterval) JSCompartment::sweep(JSContext *cx, uint32 releaseInterval)
{ {

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

@ -398,6 +398,36 @@ struct JS_FRIEND_API(JSCompartment) {
js::mjit::JaegerCompartment *jaegerCompartment; js::mjit::JaegerCompartment *jaegerCompartment;
#endif #endif
/*
* Shared scope property tree, and arena-pool for allocating its nodes.
*/
js::PropertyTree propertyTree;
#ifdef DEBUG
/* Property metering. */
jsrefcount livePropTreeNodes;
jsrefcount totalPropTreeNodes;
jsrefcount propTreeKidsChunks;
jsrefcount liveDictModeNodes;
#endif
/*
* Runtime-shared empty scopes for well-known built-in objects that lack
* class prototypes (the usual locus of an emptyShape). Mnemonic: ABCDEW
*/
js::EmptyShape *emptyArgumentsShape;
js::EmptyShape *emptyBlockShape;
js::EmptyShape *emptyCallShape;
js::EmptyShape *emptyDeclEnvShape;
js::EmptyShape *emptyEnumeratorShape;
js::EmptyShape *emptyWithShape;
typedef js::HashSet<js::EmptyShape *,
js::DefaultHasher<js::EmptyShape *>,
js::SystemAllocPolicy> EmptyShapeSet;
EmptyShapeSet emptyShapes;
bool debugMode; // true iff debug mode on bool debugMode; // true iff debug mode on
JSCList scripts; // scripts in this compartment JSCList scripts; // scripts in this compartment
@ -405,12 +435,17 @@ struct JS_FRIEND_API(JSCompartment) {
js::NativeIterCache nativeIterCache; js::NativeIterCache nativeIterCache;
JSCompartment(JSRuntime *cx); JSCompartment(JSRuntime *rt);
~JSCompartment(); ~JSCompartment();
bool init(); bool init();
/* Mark cross-compartment pointers. */
void markCrossCompartment(JSTracer *trc);
/* Mark this compartment's local roots. */
void mark(JSTracer *trc); void mark(JSTracer *trc);
bool wrap(JSContext *cx, js::Value *vp); bool wrap(JSContext *cx, js::Value *vp);
bool wrap(JSContext *cx, JSString **strp); bool wrap(JSContext *cx, JSString **strp);
bool wrap(JSContext *cx, JSObject **objp); bool wrap(JSContext *cx, JSObject **objp);
@ -441,8 +476,15 @@ struct JS_FRIEND_API(JSCompartment) {
} }
}; };
#define JS_TRACE_MONITOR(cx) (cx->compartment->traceMonitor) #define JS_TRACE_MONITOR(cx) ((cx)->compartment->traceMonitor)
#define JS_SCRIPTS_TO_GC(cx) (cx->compartment->scriptsToGC) #define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC)
#define JS_PROPERTY_TREE(cx) ((cx)->compartment->propertyTree)
#ifdef DEBUG
#define JS_COMPARTMENT_METER(x) x
#else
#define JS_COMPARTMENT_METER(x)
#endif
namespace js { namespace js {
static inline MathCache * static inline MathCache *

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

@ -595,6 +595,12 @@ DropWatchPointAndUnlock(JSContext *cx, JSWatchPoint *wp, uintN flag)
return ok; return ok;
} }
/*
* Switch to the same compartment as the watch point, since changeProperty, below,
* needs to have a compartment.
*/
SwitchToCompartment sc(cx, wp->object);
/* Remove wp from the list, then restore wp->shape->setter from wp. */ /* Remove wp from the list, then restore wp->shape->setter from wp. */
++rt->debuggerMutations; ++rt->debuggerMutations;
JS_REMOVE_LINK(&wp->links); JS_REMOVE_LINK(&wp->links);

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

@ -201,7 +201,7 @@ NewArguments(JSContext *cx, JSObject *parent, uint32 argc, JSObject &callee)
: &js_ArgumentsClass, : &js_ArgumentsClass,
proto, parent, NULL, false); proto, parent, NULL, false);
argsobj->setMap(cx->runtime->emptyArgumentsShape); argsobj->setMap(cx->compartment->emptyArgumentsShape);
argsobj->setArgsLength(argc); argsobj->setArgsLength(argc);
argsobj->setArgsData(data); argsobj->setArgsData(data);
@ -989,7 +989,7 @@ NewDeclEnvObject(JSContext *cx, JSStackFrame *fp)
return NULL; return NULL;
envobj->init(cx, &js_DeclEnvClass, NULL, &fp->scopeChain(), fp, false); envobj->init(cx, &js_DeclEnvClass, NULL, &fp->scopeChain(), fp, false);
envobj->setMap(cx->runtime->emptyDeclEnvShape); envobj->setMap(cx->compartment->emptyDeclEnvShape);
return envobj; return envobj;
} }

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

@ -854,7 +854,7 @@ js_FinishGC(JSRuntime *rt)
js_DumpGCStats(rt, stdout); js_DumpGCStats(rt, stdout);
#endif #endif
/* Delete all remaining Compartments. Ideally only the atomsCompartment should be left. */ /* Delete all remaining Compartments. */
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) { for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
JSCompartment *comp = *c; JSCompartment *comp = *c;
comp->finishArenaLists(); comp->finishArenaLists();
@ -1027,7 +1027,6 @@ JSRuntime::setGCTriggerFactor(uint32 factor)
for (JSCompartment **c = compartments.begin(); c != compartments.end(); ++c) { for (JSCompartment **c = compartments.begin(); c != compartments.end(); ++c) {
(*c)->setGCLastBytes(gcLastBytes); (*c)->setGCLastBytes(gcLastBytes);
} }
atomsCompartment->setGCLastBytes(gcLastBytes);
} }
void void
@ -1731,19 +1730,6 @@ MarkRuntime(JSTracer *trc)
for (ThreadDataIter i(rt); !i.empty(); i.popFront()) for (ThreadDataIter i(rt); !i.empty(); i.popFront())
i.threadData()->mark(trc); i.threadData()->mark(trc);
if (rt->emptyArgumentsShape)
rt->emptyArgumentsShape->trace(trc);
if (rt->emptyBlockShape)
rt->emptyBlockShape->trace(trc);
if (rt->emptyCallShape)
rt->emptyCallShape->trace(trc);
if (rt->emptyDeclEnvShape)
rt->emptyDeclEnvShape->trace(trc);
if (rt->emptyEnumeratorShape)
rt->emptyEnumeratorShape->trace(trc);
if (rt->emptyWithShape)
rt->emptyWithShape->trace(trc);
/* /*
* We mark extra roots at the last thing so it can use use additional * We mark extra roots at the last thing so it can use use additional
* colors to implement cycle collection. * colors to implement cycle collection.
@ -2247,7 +2233,7 @@ PreGCCleanup(JSContext *cx, JSGCInvocationKind gckind)
#endif #endif
) { ) {
rt->gcRegenShapes = true; rt->gcRegenShapes = true;
rt->shapeGen = Shape::LAST_RESERVED_SHAPE; rt->shapeGen = 0;
rt->protoHazardShape = 0; rt->protoHazardShape = 0;
} }
@ -2287,7 +2273,9 @@ MarkAndSweepCompartment(JSContext *cx, JSCompartment *comp, JSGCInvocationKind g
r.front()->clearMarkBitmap(); r.front()->clearMarkBitmap();
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
(*c)->mark(&gcmarker); (*c)->markCrossCompartment(&gcmarker);
comp->mark(&gcmarker);
MarkRuntime(&gcmarker); MarkRuntime(&gcmarker);
@ -2358,11 +2346,13 @@ MarkAndSweepCompartment(JSContext *cx, JSCompartment *comp, JSGCInvocationKind g
comp->finalizeStringArenaLists(cx); comp->finalizeStringArenaLists(cx);
TIMESTAMP(sweepStringEnd); TIMESTAMP(sweepStringEnd);
/* #ifdef DEBUG
* Unmark the runtime's property trees because we don't /* Make sure that we didn't mark a Shape in another compartment. */
* sweep them. for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
*/ JS_ASSERT_IF(*c != comp, (*c)->propertyTree.checkShapesAllUnmarked(cx));
js::PropertyTree::unmarkShapes(cx); }
comp->propertyTree.dumpShapes(cx);
#endif
/* /*
* Destroy arenas after we finished the sweeping so finalizers can safely * Destroy arenas after we finished the sweeping so finalizers can safely
@ -2400,6 +2390,9 @@ MarkAndSweep(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM)
for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront()) for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront())
r.front()->clearMarkBitmap(); r.front()->clearMarkBitmap();
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
(*c)->mark(&gcmarker);
MarkRuntime(&gcmarker); MarkRuntime(&gcmarker);
js_MarkScriptFilenames(rt); js_MarkScriptFilenames(rt);
@ -2469,13 +2462,19 @@ MarkAndSweep(JSContext *cx, JSGCInvocationKind gckind GCTIMER_PARAM)
TIMESTAMP(sweepStringEnd); TIMESTAMP(sweepStringEnd);
SweepCompartments(cx, gckind);
/* /*
* Sweep the runtime's property trees after finalizing objects, in case any * Sweep the runtime's property trees after finalizing objects, in case any
* had watchpoints referencing tree nodes. * had watchpoints referencing tree nodes.
*
* Do this before sweeping compartments, so that we sweep all shapes in
* unreachable compartments.
*/ */
js::PropertyTree::sweepShapes(cx); for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
(*c)->propertyTree.sweepShapes(cx);
(*c)->propertyTree.dumpShapes(cx);
}
SweepCompartments(cx, gckind);
/* /*
* Sweep script filenames after sweeping functions in the generic loop * Sweep script filenames after sweeping functions in the generic loop
@ -2702,6 +2701,12 @@ GCUntilDone(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIM
AutoGCSession gcsession(cx); AutoGCSession gcsession(cx);
/*
* We should not be depending on cx->compartment in the GC, so set it to
* NULL to look for violations.
*/
SwitchToCompartment(cx, (JSCompartment *)NULL);
JS_ASSERT(!rt->gcCurrentCompartment); JS_ASSERT(!rt->gcCurrentCompartment);
rt->gcCurrentCompartment = comp; rt->gcCurrentCompartment = comp;

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

@ -427,7 +427,7 @@ NewIteratorObject(JSContext *cx, uintN flags)
if (!obj) if (!obj)
return false; return false;
obj->init(cx, &js_IteratorClass, NULL, NULL, NULL, false); obj->init(cx, &js_IteratorClass, NULL, NULL, NULL, false);
obj->setMap(cx->runtime->emptyEnumeratorShape); obj->setMap(cx->compartment->emptyEnumeratorShape);
return obj; return obj;
} }

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

@ -3209,7 +3209,7 @@ js_NewWithObject(JSContext *cx, JSObject *proto, JSObject *parent, jsint depth)
JSStackFrame *priv = js_FloatingFrameIfGenerator(cx, cx->fp()); JSStackFrame *priv = js_FloatingFrameIfGenerator(cx, cx->fp());
obj->init(cx, &js_WithClass, proto, parent, priv, false); obj->init(cx, &js_WithClass, proto, parent, priv, false);
obj->setMap(cx->runtime->emptyWithShape); obj->setMap(cx->compartment->emptyWithShape);
OBJ_SET_BLOCK_DEPTH(cx, obj, depth); OBJ_SET_BLOCK_DEPTH(cx, obj, depth);
AutoObjectRooter tvr(cx, obj); AutoObjectRooter tvr(cx, obj);
@ -3235,7 +3235,7 @@ js_NewBlockObject(JSContext *cx)
return NULL; return NULL;
blockObj->init(cx, &js_BlockClass, NULL, NULL, NULL, false); blockObj->init(cx, &js_BlockClass, NULL, NULL, NULL, false);
blockObj->setMap(cx->runtime->emptyBlockShape); blockObj->setMap(cx->compartment->emptyBlockShape);
return blockObj; return blockObj;
} }
@ -4682,7 +4682,7 @@ js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, const Value &valu
* member declaration. * member declaration.
*/ */
if (obj->isDelegate() && (attrs & (JSPROP_READONLY | JSPROP_SETTER))) if (obj->isDelegate() && (attrs & (JSPROP_READONLY | JSPROP_SETTER)))
cx->runtime->protoHazardShape = js_GenerateShape(cx, false); cx->runtime->protoHazardShape = js_GenerateShape(cx);
/* Use the object's class getter and setter by default. */ /* Use the object's class getter and setter by default. */
clasp = obj->getClass(); clasp = obj->getClass();

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

@ -316,6 +316,10 @@ struct JSObject : js::gc::Cell {
* for DictionaryProperties assert that the scope is in dictionary mode and * for DictionaryProperties assert that the scope is in dictionary mode and
* any reachable properties are flagged as dictionary properties. * any reachable properties are flagged as dictionary properties.
* *
* For native objects, this field is always a Shape. For non-native objects,
* it points to the singleton sharedNonNative JSObjectMap, whose shape field
* is SHAPELESS.
*
* NB: these private methods do *not* update this scope's shape to track * NB: these private methods do *not* update this scope's shape to track
* lastProp->shape after they finish updating the linked list in the case * lastProp->shape after they finish updating the linked list in the case
* where lastProp is updated. It is up to calling code in jsscope.cpp to * where lastProp is updated. It is up to calling code in jsscope.cpp to

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

@ -79,69 +79,62 @@ PropertyTree::finish()
JS_FinishArenaPool(&arenaPool); JS_FinishArenaPool(&arenaPool);
} }
/* /* On failure, returns NULL. Does not report out of memory. */
* NB: Called with cx->runtime->gcLock held if gcLocked is true.
* On failure, return null after unlocking the GC and reporting out of memory.
*/
Shape * Shape *
PropertyTree::newShape(JSContext *cx, bool gcLocked) PropertyTree::newShapeUnchecked()
{ {
Shape *shape; Shape *shape;
if (!gcLocked)
JS_LOCK_GC(cx->runtime);
shape = freeList; shape = freeList;
if (shape) { if (shape) {
shape->removeFree(); shape->removeFree();
} else { } else {
JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape)); JS_ARENA_ALLOCATE_CAST(shape, Shape *, &arenaPool, sizeof(Shape));
if (!shape) { if (!shape)
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx);
return NULL; return NULL;
}
} }
if (!gcLocked)
JS_UNLOCK_GC(cx->runtime);
JS_RUNTIME_METER(cx->runtime, livePropTreeNodes); #ifdef DEBUG
JS_RUNTIME_METER(cx->runtime, totalPropTreeNodes); shape->compartment = compartment;
#endif
JS_COMPARTMENT_METER(compartment->livePropTreeNodes++);
JS_COMPARTMENT_METER(compartment->totalPropTreeNodes++);
return shape; return shape;
} }
/* Shape *
* NB: Called with cx->runtime->gcLock held, always. PropertyTree::newShape(JSContext *cx)
* On failure, return null after unlocking the GC and reporting out of memory.
*/
KidsChunk *
KidsChunk::create(JSContext *cx)
{ {
KidsChunk *chunk; Shape *shape = newShapeUnchecked();
if (!shape)
chunk = (KidsChunk *) js_calloc(sizeof *chunk);
if (!chunk) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx); JS_ReportOutOfMemory(cx);
return shape;
}
static KidsHash *
HashChildren(Shape *kid1, Shape *kid2)
{
void *mem = js_malloc(sizeof(KidsHash));
if (!mem)
return NULL;
KidsHash *hash = new (mem) KidsHash();
if (!hash->init(2)) {
js_free(hash);
return NULL; return NULL;
} }
JS_RUNTIME_METER(cx->runtime, propTreeKidsChunks);
return chunk; KidsHash::AddPtr addPtr = hash->lookupForAdd(kid1);
JS_ALWAYS_TRUE(hash->add(addPtr, kid1));
addPtr = hash->lookupForAdd(kid2);
JS_ASSERT(!addPtr.found());
JS_ALWAYS_TRUE(hash->add(addPtr, kid2));
return hash;
} }
KidsChunk *
KidsChunk::destroy(JSContext *cx, KidsChunk *chunk)
{
JS_RUNTIME_UNMETER(cx->runtime, propTreeKidsChunks);
KidsChunk *nextChunk = chunk->next;
js_free(chunk);
return nextChunk;
}
/*
* NB: Called with cx->runtime->gcLock held, always.
* On failure, return false after unlocking the GC and reporting out of memory.
*/
bool bool
PropertyTree::insertChild(JSContext *cx, Shape *parent, Shape *child) PropertyTree::insertChild(JSContext *cx, Shape *parent, Shape *child)
{ {
@ -150,89 +143,45 @@ PropertyTree::insertChild(JSContext *cx, Shape *parent, Shape *child)
JS_ASSERT(!child->inDictionary()); JS_ASSERT(!child->inDictionary());
JS_ASSERT(!JSID_IS_VOID(parent->id)); JS_ASSERT(!JSID_IS_VOID(parent->id));
JS_ASSERT(!JSID_IS_VOID(child->id)); JS_ASSERT(!JSID_IS_VOID(child->id));
JS_ASSERT(cx->compartment == compartment);
child->setParent(parent); JS_ASSERT(child->compartment == parent->compartment);
KidsPointer *kidp = &parent->kids; KidsPointer *kidp = &parent->kids;
if (kidp->isNull()) { if (kidp->isNull()) {
child->setParent(parent);
kidp->setShape(child); kidp->setShape(child);
return true; return true;
} }
Shape *shape;
if (kidp->isShape()) { if (kidp->isShape()) {
shape = kidp->toShape(); Shape *shape = kidp->toShape();
JS_ASSERT(shape != child); JS_ASSERT(shape != child);
if (shape->matches(child)) { JS_ASSERT(!shape->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);
}
KidsChunk *chunk = KidsChunk::create(cx); KidsHash *hash = HashChildren(shape, child);
if (!chunk) if (!hash) {
return false;
parent->kids.setChunk(chunk);
chunk->kids[0] = shape;
chunk->kids[1] = child;
return true;
}
if (kidp->isChunk()) {
KidsChunk **chunkp;
KidsChunk *chunk = kidp->toChunk();
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
shape = chunk->kids[i];
if (!shape) {
chunk->kids[i] = child;
return true;
}
JS_ASSERT(shape != child);
if (shape->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(shape != child);
JS_RUNTIME_METER(cx->runtime, duplicatePropTreeNodes);
}
}
chunkp = &chunk->next;
} while ((chunk = *chunkp) != NULL);
chunk = KidsChunk::create(cx);
if (!chunk)
return false;
*chunkp = chunk;
chunk->kids[0] = child;
return true;
}
KidsHash *hash = kidp->toHash();
KidsHash::AddPtr addPtr = hash->lookupForAdd(child);
if (!addPtr) {
if (!hash->add(addPtr, child)) {
JS_UNLOCK_GC(cx->runtime);
JS_ReportOutOfMemory(cx); JS_ReportOutOfMemory(cx);
return false; return false;
} }
} else { kidp->setHash(hash);
// FIXME ignore duplicate child case here, going thread-local soon! child->setParent(parent);
return true;
} }
KidsHash *hash = kidp->toHash();
KidsHash::AddPtr addPtr = hash->lookupForAdd(child);
JS_ASSERT(!addPtr.found());
if (!hash->add(addPtr, child)) {
JS_ReportOutOfMemory(cx);
return false;
}
child->setParent(parent);
return true; return true;
} }
/* NB: Called with cx->runtime->gcLock held. */
void void
PropertyTree::removeChild(JSContext *cx, Shape *child) PropertyTree::removeChild(Shape *child)
{ {
JS_ASSERT(!child->inDictionary()); JS_ASSERT(!child->inDictionary());
@ -248,97 +197,9 @@ PropertyTree::removeChild(JSContext *cx, Shape *child)
return; return;
} }
if (kidp->isChunk()) {
KidsChunk *list = kidp->toChunk();
KidsChunk *chunk = list;
KidsChunk **chunkp = &list;
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
if (chunk->kids[i] == child) {
KidsChunk *lastChunk = chunk;
uintN j;
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.setNull();
KidsChunk::destroy(cx, lastChunk);
}
return;
}
}
chunkp = &chunk->next;
} while ((chunk = *chunkp) != NULL);
return;
}
kidp->toHash()->remove(child); kidp->toHash()->remove(child);
} }
static KidsHash *
HashChunks(KidsChunk *chunk, uintN n)
{
void *mem = js_malloc(sizeof(KidsHash));
if (!mem)
return NULL;
KidsHash *hash = new (mem) KidsHash();
if (!hash->init(n)) {
js_free(hash);
return NULL;
}
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
Shape *shape = chunk->kids[i];
if (!shape)
break;
KidsHash::AddPtr addPtr = hash->lookupForAdd(shape);
if (!addPtr) {
/*
* Infallible, we right-sized via hash->init(n) just above.
* Assert just in case jshashtable.h ever regresses.
*/
JS_ALWAYS_TRUE(hash->add(addPtr, shape));
} else {
/*
* Duplicate child case, we don't handle this race,
* multi-threaded shapes are going away...
*/
}
}
} while ((chunk = chunk->next) != NULL);
return hash;
}
/*
* 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.
*/
Shape * Shape *
PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child) PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
{ {
@ -348,95 +209,36 @@ PropertyTree::getChild(JSContext *cx, Shape *parent, const Shape &child)
JS_ASSERT(!JSID_IS_VOID(parent->id)); JS_ASSERT(!JSID_IS_VOID(parent->id));
/* /*
* Because chunks are appended at the end and never deleted except by * The property tree has extremely low fan-out below its root in
* the GC, we can search without taking the runtime's GC lock. We may * popular embeddings with real-world workloads. Patterns such as
* miss a matching shape added by another thread, and make a duplicate * defining closures that capture a constructor's environment as
* one, but that is an unlikely, therefore small, cost. The property * getters or setters on the new object that is passed in as
* tree has extremely low fan-out below its root in popular embeddings * |this| can significantly increase fan-out below the property
* 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. * tree root -- see bug 335700 for details.
*/ */
KidsPointer *kidp = &parent->kids; KidsPointer *kidp = &parent->kids;
if (!kidp->isNull()) { if (kidp->isShape()) {
if (kidp->isShape()) { shape = kidp->toShape();
shape = kidp->toShape(); if (shape->matches(&child))
if (shape->matches(&child)) return shape;
return shape; } else if (kidp->isHash()) {
} else if (kidp->isChunk()) { shape = *kidp->toHash()->lookup(&child);
KidsChunk *chunk = kidp->toChunk(); if (shape)
return shape;
uintN n = 0; } else {
do { /* If kidp->isNull(), we always insert. */
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
shape = chunk->kids[i];
if (!shape) {
n += i;
if (n >= CHUNK_HASH_THRESHOLD) {
/*
* kidp->isChunk() was true, but if we're racing it
* may not be by this point. FIXME: thread "safety"
* is for the birds!
*/
if (!kidp->isHash()) {
chunk = kidp->toChunk();
KidsHash *hash = HashChunks(chunk, n);
if (!hash) {
JS_ReportOutOfMemory(cx);
return NULL;
}
JS_LOCK_GC(cx->runtime);
if (kidp->isHash()) {
hash->~KidsHash();
js_free(hash);
} else {
// FIXME unsafe race with kidp->is/toChunk() above.
// But this is all going single-threaded soon...
while (chunk)
chunk = KidsChunk::destroy(cx, chunk);
kidp->setHash(hash);
}
goto locked_not_found;
}
}
goto not_found;
}
if (shape->matches(&child))
return shape;
}
n += MAX_KIDS_PER_CHUNK;
} while ((chunk = chunk->next) != NULL);
} else {
JS_LOCK_GC(cx->runtime);
shape = *kidp->toHash()->lookup(&child);
if (shape)
goto out;
goto locked_not_found;
}
} }
not_found: shape = newShape(cx);
JS_LOCK_GC(cx->runtime);
locked_not_found:
shape = newShape(cx, true);
if (!shape) if (!shape)
return NULL; return NULL;
new (shape) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs, new (shape) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs,
child.flags, child.shortid, js_GenerateShape(cx, true)); child.flags, child.shortid, js_GenerateShape(cx));
if (!insertChild(cx, parent, shape)) if (!insertChild(cx, parent, shape))
return NULL; return NULL;
out:
JS_UNLOCK_GC(cx->runtime);
return shape; return shape;
} }
@ -447,23 +249,6 @@ KidsPointer::checkConsistency(const Shape *aKid) const
{ {
if (isShape()) { if (isShape()) {
JS_ASSERT(toShape() == aKid); JS_ASSERT(toShape() == aKid);
} else if (isChunk()) {
bool found = false;
for (KidsChunk *chunk = toChunk(); chunk; chunk = chunk->next) {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
if (!chunk->kids[i]) {
JS_ASSERT(!chunk->next);
for (uintN j = i + 1; j < MAX_KIDS_PER_CHUNK; j++)
JS_ASSERT(!chunk->kids[j]);
break;
}
if (chunk->kids[i] == aKid) {
JS_ASSERT(!found);
found = true;
}
}
}
JS_ASSERT(found);
} else { } else {
JS_ASSERT(isHash()); JS_ASSERT(isHash());
KidsHash *hash = toHash(); KidsHash *hash = toHash();
@ -541,29 +326,17 @@ void
js::PropertyTree::meter(JSBasicStats *bs, Shape *node) js::PropertyTree::meter(JSBasicStats *bs, Shape *node)
{ {
uintN nkids = 0; uintN nkids = 0;
const KidsPointer &kids = node->kids; const KidsPointer &kidp = node->kids;
if (!kids.isNull()) { if (kidp.isShape()) {
if (kids.isShape()) { meter(bs, kidp.toShape());
meter(bs, kids.toShape()); nkids = 1;
nkids = 1; } else if (kidp.isHash()) {
} else if (kids.isChunk()) { const KidsHash &hash = *kidp.toHash();
for (KidsChunk *chunk = kids.toChunk(); chunk; chunk = chunk->next) { for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) { Shape *kid = range.front();
Shape *kid = chunk->kids[i];
if (!kid) meter(bs, kid);
break; nkids++;
meter(bs, kid);
nkids++;
}
}
} else {
const KidsHash &hash = *kids.toHash();
for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
Shape *kid = range.front();
meter(bs, kid);
nkids++;
}
} }
} }
@ -588,17 +361,6 @@ Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
Shape *kid = kids.toShape(); Shape *kid = kids.toShape();
JS_ASSERT(kid->parent == this); JS_ASSERT(kid->parent == this);
kid->dumpSubtree(cx, level, fp); kid->dumpSubtree(cx, level, fp);
} else if (kids.isChunk()) {
KidsChunk *chunk = kids.toChunk();
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
Shape *kid = chunk->kids[i];
if (!kid)
break;
JS_ASSERT(kid->parent == this);
kid->dumpSubtree(cx, level, fp);
}
} while ((chunk = chunk->next) != NULL);
} else { } else {
const KidsHash &hash = *kids.toHash(); const KidsHash &hash = *kids.toHash();
for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) { for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) {
@ -614,20 +376,12 @@ Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const
#endif /* DEBUG */ #endif /* DEBUG */
JS_ALWAYS_INLINE void JS_ALWAYS_INLINE void
js::PropertyTree::orphanKids(JSContext *cx, Shape *shape) js::PropertyTree::orphanChildren(Shape *shape)
{ {
KidsPointer *kidp = &shape->kids; KidsPointer *kidp = &shape->kids;
JS_ASSERT(!kidp->isNull()); JS_ASSERT(!kidp->isNull());
/*
* Note that JS_PROPERTY_TREE(cx).removeChild(cx, shape) precedes the call
* to orphanKids in sweepShapes, below. Therefore the grandparent must have
* either no kids left, or else space in chunks or a hash for more than one
* kid.
*/
JS_ASSERT_IF(shape->parent, !shape->parent->kids.isShape());
if (kidp->isShape()) { if (kidp->isShape()) {
Shape *kid = kidp->toShape(); Shape *kid = kidp->toShape();
@ -635,21 +389,6 @@ js::PropertyTree::orphanKids(JSContext *cx, Shape *shape)
JS_ASSERT(kid->parent == shape); JS_ASSERT(kid->parent == shape);
kid->parent = NULL; kid->parent = NULL;
} }
} else if (kidp->isChunk()) {
KidsChunk *chunk = kidp->toChunk();
do {
for (uintN i = 0; i < MAX_KIDS_PER_CHUNK; i++) {
Shape *kid = chunk->kids[i];
if (!kid)
break;
if (!JSID_IS_VOID(kid->id)) {
JS_ASSERT(kid->parent == shape);
kid->parent = NULL;
}
}
} while ((chunk = KidsChunk::destroy(cx, chunk)) != NULL);
} else { } else {
KidsHash *hash = kidp->toHash(); KidsHash *hash = kidp->toHash();
@ -671,12 +410,14 @@ js::PropertyTree::orphanKids(JSContext *cx, Shape *shape)
void void
js::PropertyTree::sweepShapes(JSContext *cx) js::PropertyTree::sweepShapes(JSContext *cx)
{ {
JSRuntime *rt = compartment->rt;
#ifdef DEBUG #ifdef DEBUG
JSBasicStats bs; JSBasicStats bs;
uint32 livePropCapacity = 0, totalLiveCount = 0; uint32 livePropCapacity = 0, totalLiveCount = 0;
static FILE *logfp; static FILE *logfp;
if (!logfp) { if (!logfp) {
if (const char *filename = cx->runtime->propTreeStatFilename) if (const char *filename = rt->propTreeStatFilename)
logfp = fopen(filename, "w"); logfp = fopen(filename, "w");
} }
@ -685,18 +426,18 @@ js::PropertyTree::sweepShapes(JSContext *cx)
uint32 empties; uint32 empties;
{ {
typedef JSRuntime::EmptyShapeSet HS; typedef JSCompartment::EmptyShapeSet HS;
HS &h = cx->runtime->emptyShapes; HS &h = compartment->emptyShapes;
empties = h.count(); empties = h.count();
MeterKidCount(&bs, empties); MeterKidCount(&bs, empties);
for (HS::Range r = h.all(); !r.empty(); r.popFront()) for (HS::Range r = h.all(); !r.empty(); r.popFront())
meter(&bs, r.front()); meter(&bs, r.front());
} }
double props = cx->runtime->liveObjectPropsPreSweep; double props = rt->liveObjectPropsPreSweep;
double nodes = cx->runtime->livePropTreeNodes; double nodes = compartment->livePropTreeNodes;
double dicts = cx->runtime->liveDictModeNodes; double dicts = compartment->liveDictModeNodes;
/* Empty scope nodes are never hashed, so subtract them from nodes. */ /* Empty scope nodes are never hashed, so subtract them from nodes. */
JS_ASSERT(nodes - dicts == bs.sum); JS_ASSERT(nodes - dicts == bs.sum);
@ -718,7 +459,7 @@ js::PropertyTree::sweepShapes(JSContext *cx)
* already GC'ed from the root ply, but we will avoid re-orphaning their * already GC'ed from the root ply, but we will avoid re-orphaning their
* kids, because the kids member will already be null. * kids, because the kids member will already be null.
*/ */
JSArena **ap = &JS_PROPERTY_TREE(cx).arenaPool.first.next; JSArena **ap = &arenaPool.first.next;
while (JSArena *a = *ap) { while (JSArena *a = *ap) {
Shape *limit = (Shape *) a->avail; Shape *limit = (Shape *) a->avail;
uintN liveCount = 0; uintN liveCount = 0;
@ -738,11 +479,11 @@ js::PropertyTree::sweepShapes(JSContext *cx)
*/ */
if (shape->marked()) { if (shape->marked()) {
shape->clearMark(); shape->clearMark();
if (cx->runtime->gcRegenShapes) { if (rt->gcRegenShapes) {
if (shape->hasRegenFlag()) if (shape->hasRegenFlag())
shape->clearRegenFlag(); shape->clearRegenFlag();
else else
shape->shape = js_RegenerateShapeForGC(cx); shape->shape = js_RegenerateShapeForGC(rt);
} }
liveCount++; liveCount++;
continue; continue;
@ -750,18 +491,18 @@ js::PropertyTree::sweepShapes(JSContext *cx)
#ifdef DEBUG #ifdef DEBUG
if ((shape->flags & Shape::SHARED_EMPTY) && if ((shape->flags & Shape::SHARED_EMPTY) &&
cx->runtime->meterEmptyShapes()) { rt->meterEmptyShapes()) {
cx->runtime->emptyShapes.remove((EmptyShape *) shape); compartment->emptyShapes.remove((EmptyShape *) shape);
} }
#endif #endif
if (shape->inDictionary()) { if (shape->inDictionary()) {
JS_RUNTIME_UNMETER(cx->runtime, liveDictModeNodes); JS_COMPARTMENT_METER(compartment->liveDictModeNodes--);
} else { } else {
/* /*
* Here, shape is garbage to collect, but its parent might not * Here, shape is garbage to collect, but its parent might not
* be, so we may have to remove it from its parent's kids hash, * be, so we may have to remove it from its parent's kids hash
* chunk list, or kid singleton pointer set. * or kid singleton pointer set.
* *
* Without a separate mark-clearing pass, we can't tell whether * Without a separate mark-clearing pass, we can't tell whether
* shape->parent is live at this point, so we must remove shape * shape->parent is live at this point, so we must remove shape
@ -771,10 +512,10 @@ js::PropertyTree::sweepShapes(JSContext *cx)
* tree node's kids' parent links when sweeping that node. * tree node's kids' parent links when sweeping that node.
*/ */
if (shape->parent) if (shape->parent)
JS_PROPERTY_TREE(cx).removeChild(cx, shape); removeChild(shape);
if (!shape->kids.isNull()) if (!shape->kids.isNull())
orphanKids(cx, shape); orphanChildren(shape);
} }
/* /*
@ -782,15 +523,15 @@ js::PropertyTree::sweepShapes(JSContext *cx)
* shape is on the freelist. * shape is on the freelist.
*/ */
shape->freeTable(cx); shape->freeTable(cx);
shape->insertFree(&JS_PROPERTY_TREE(cx).freeList); shape->insertFree(&freeList);
JS_RUNTIME_UNMETER(cx->runtime, livePropTreeNodes); JS_COMPARTMENT_METER(compartment->livePropTreeNodes--);
} }
/* If a contains no live properties, return it to the malloc heap. */ /* If a contains no live properties, return it to the malloc heap. */
if (liveCount == 0) { if (liveCount == 0) {
for (Shape *shape = (Shape *) a->base; shape < limit; shape++) for (Shape *shape = (Shape *) a->base; shape < limit; shape++)
shape->removeFree(); shape->removeFree();
JS_ARENA_DESTROY(&JS_PROPERTY_TREE(cx).arenaPool, a, ap); JS_ARENA_DESTROY(&arenaPool, a, ap);
} else { } else {
#ifdef DEBUG #ifdef DEBUG
livePropCapacity += limit - (Shape *) a->base; livePropCapacity += limit - (Shape *) a->base;
@ -804,7 +545,7 @@ js::PropertyTree::sweepShapes(JSContext *cx)
if (logfp) { if (logfp) {
fprintf(logfp, fprintf(logfp,
"\nProperty tree stats for gcNumber %lu\n", "\nProperty tree stats for gcNumber %lu\n",
(unsigned long) cx->runtime->gcNumber); (unsigned long) rt->gcNumber);
fprintf(logfp, "arenautil %g%%\n", fprintf(logfp, "arenautil %g%%\n",
(totalLiveCount && livePropCapacity) (totalLiveCount && livePropCapacity)
@ -813,93 +554,77 @@ js::PropertyTree::sweepShapes(JSContext *cx)
#define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0) #define RATE(f1, f2) (((double)js_scope_stats.f1 / js_scope_stats.f2) * 100.0)
fprintf(logfp, /* This data is global, so only print it once per GC. */
"Scope search stats:\n" if (compartment == rt->atomsCompartment) {
" searches: %6u\n" fprintf(logfp,
" hits: %6u %5.2f%% of searches\n" "Scope search stats:\n"
" misses: %6u %5.2f%%\n" " searches: %6u\n"
" hashes: %6u %5.2f%%\n" " hits: %6u %5.2f%% of searches\n"
" hashHits: %6u %5.2f%% (%5.2f%% of hashes)\n" " misses: %6u %5.2f%%\n"
" hashMisses: %6u %5.2f%% (%5.2f%%)\n" " hashes: %6u %5.2f%%\n"
" steps: %6u %5.2f%% (%5.2f%%)\n" " hashHits: %6u %5.2f%% (%5.2f%% of hashes)\n"
" stepHits: %6u %5.2f%% (%5.2f%%)\n" " hashMisses: %6u %5.2f%% (%5.2f%%)\n"
" stepMisses: %6u %5.2f%% (%5.2f%%)\n" " steps: %6u %5.2f%% (%5.2f%%)\n"
" initSearches: %6u\n" " stepHits: %6u %5.2f%% (%5.2f%%)\n"
" changeSearches: %6u\n" " stepMisses: %6u %5.2f%% (%5.2f%%)\n"
" tableAllocFails: %6u\n" " initSearches: %6u\n"
" toDictFails: %6u\n" " changeSearches: %6u\n"
" wrapWatchFails: %6u\n" " tableAllocFails: %6u\n"
" adds: %6u\n" " toDictFails: %6u\n"
" addFails: %6u\n" " wrapWatchFails: %6u\n"
" puts: %6u\n" " adds: %6u\n"
" redundantPuts: %6u\n" " addFails: %6u\n"
" putFails: %6u\n" " puts: %6u\n"
" changes: %6u\n" " redundantPuts: %6u\n"
" changeFails: %6u\n" " putFails: %6u\n"
" compresses: %6u\n" " changes: %6u\n"
" grows: %6u\n" " changeFails: %6u\n"
" removes: %6u\n" " compresses: %6u\n"
" removeFrees: %6u\n" " grows: %6u\n"
" uselessRemoves: %6u\n" " removes: %6u\n"
" shrinks: %6u\n", " removeFrees: %6u\n"
js_scope_stats.searches, " uselessRemoves: %6u\n"
js_scope_stats.hits, RATE(hits, searches), " shrinks: %6u\n",
js_scope_stats.misses, RATE(misses, searches), js_scope_stats.searches,
js_scope_stats.hashes, RATE(hashes, searches), js_scope_stats.hits, RATE(hits, searches),
js_scope_stats.hashHits, RATE(hashHits, searches), RATE(hashHits, hashes), js_scope_stats.misses, RATE(misses, searches),
js_scope_stats.hashMisses, RATE(hashMisses, searches), RATE(hashMisses, hashes), js_scope_stats.hashes, RATE(hashes, searches),
js_scope_stats.steps, RATE(steps, searches), RATE(steps, hashes), js_scope_stats.hashHits, RATE(hashHits, searches), RATE(hashHits, hashes),
js_scope_stats.stepHits, RATE(stepHits, searches), RATE(stepHits, hashes), js_scope_stats.hashMisses, RATE(hashMisses, searches), RATE(hashMisses, hashes),
js_scope_stats.stepMisses, RATE(stepMisses, searches), RATE(stepMisses, hashes), js_scope_stats.steps, RATE(steps, searches), RATE(steps, hashes),
js_scope_stats.initSearches, js_scope_stats.stepHits, RATE(stepHits, searches), RATE(stepHits, hashes),
js_scope_stats.changeSearches, js_scope_stats.stepMisses, RATE(stepMisses, searches), RATE(stepMisses, hashes),
js_scope_stats.tableAllocFails, js_scope_stats.initSearches,
js_scope_stats.toDictFails, js_scope_stats.changeSearches,
js_scope_stats.wrapWatchFails, js_scope_stats.tableAllocFails,
js_scope_stats.adds, js_scope_stats.toDictFails,
js_scope_stats.addFails, js_scope_stats.wrapWatchFails,
js_scope_stats.puts, js_scope_stats.adds,
js_scope_stats.redundantPuts, js_scope_stats.addFails,
js_scope_stats.putFails, js_scope_stats.puts,
js_scope_stats.changes, js_scope_stats.redundantPuts,
js_scope_stats.changeFails, js_scope_stats.putFails,
js_scope_stats.compresses, js_scope_stats.changes,
js_scope_stats.grows, js_scope_stats.changeFails,
js_scope_stats.removes, js_scope_stats.compresses,
js_scope_stats.removeFrees, js_scope_stats.grows,
js_scope_stats.uselessRemoves, js_scope_stats.removes,
js_scope_stats.shrinks); js_scope_stats.removeFrees,
js_scope_stats.uselessRemoves,
js_scope_stats.shrinks);
}
#undef RATE #undef RATE
fflush(logfp); fflush(logfp);
} }
if (const char *filename = cx->runtime->propTreeDumpFilename) {
char pathname[1024];
JS_snprintf(pathname, sizeof pathname, "%s.%lu",
filename, (unsigned long)cx->runtime->gcNumber);
FILE *dumpfp = fopen(pathname, "w");
if (dumpfp) {
typedef JSRuntime::EmptyShapeSet HS;
HS &h = cx->runtime->emptyShapes;
for (HS::Range r = h.all(); !r.empty(); r.popFront()) {
Shape *empty = r.front();
empty->dumpSubtree(cx, 0, dumpfp);
putc('\n', dumpfp);
}
fclose(dumpfp);
}
}
#endif /* DEBUG */ #endif /* DEBUG */
} }
void bool
js::PropertyTree::unmarkShapes(JSContext *cx) js::PropertyTree::checkShapesAllUnmarked(JSContext *cx)
{ {
JSArena **ap = &JS_PROPERTY_TREE(cx).arenaPool.first.next; JSArena **ap = &arenaPool.first.next;
while (JSArena *a = *ap) { while (JSArena *a = *ap) {
Shape *limit = (Shape *) a->avail; Shape *limit = (Shape *) a->avail;
@ -909,8 +634,37 @@ js::PropertyTree::unmarkShapes(JSContext *cx)
continue; continue;
if (shape->marked()) if (shape->marked())
shape->clearMark(); return false;
} }
ap = &a->next; ap = &a->next;
} }
return true;
}
void
js::PropertyTree::dumpShapes(JSContext *cx)
{
#ifdef DEBUG
JSRuntime *rt = compartment->rt;
if (const char *filename = rt->propTreeDumpFilename) {
char pathname[1024];
JS_snprintf(pathname, sizeof pathname, "%s.%lu",
filename, (unsigned long)rt->gcNumber);
FILE *dumpfp = fopen(pathname, "w");
if (dumpfp) {
typedef JSCompartment::EmptyShapeSet HS;
HS &h = compartment->emptyShapes;
for (HS::Range r = h.all(); !r.empty(); r.popFront()) {
Shape *empty = r.front();
empty->dumpSubtree(cx, 0, dumpfp);
putc('\n', dumpfp);
}
fclose(dumpfp);
}
}
#endif
} }

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

@ -46,19 +46,6 @@
namespace js { namespace js {
enum {
MAX_KIDS_PER_CHUNK = 10U,
CHUNK_HASH_THRESHOLD = 30U
};
struct KidsChunk {
js::Shape *kids[MAX_KIDS_PER_CHUNK];
KidsChunk *next;
static KidsChunk *create(JSContext *cx);
static KidsChunk *destroy(JSContext *cx, KidsChunk *chunk);
};
struct ShapeHasher { struct ShapeHasher {
typedef js::Shape *Key; typedef js::Shape *Key;
typedef const js::Shape *Lookup; typedef const js::Shape *Lookup;
@ -73,9 +60,8 @@ class KidsPointer {
private: private:
enum { enum {
SHAPE = 0, SHAPE = 0,
CHUNK = 1, HASH = 1,
HASH = 2, TAG = 1
TAG = 3
}; };
jsuword w; jsuword w;
@ -84,7 +70,6 @@ class KidsPointer {
bool isNull() const { return !w; } bool isNull() const { return !w; }
void setNull() { w = 0; } void setNull() { w = 0; }
bool isShapeOrNull() const { return (w & TAG) == SHAPE; }
bool isShape() const { return (w & TAG) == SHAPE && !isNull(); } bool isShape() const { return (w & TAG) == SHAPE && !isNull(); }
js::Shape *toShape() const { js::Shape *toShape() const {
JS_ASSERT(isShape()); JS_ASSERT(isShape());
@ -96,17 +81,6 @@ class KidsPointer {
w = reinterpret_cast<jsuword>(shape) | SHAPE; w = reinterpret_cast<jsuword>(shape) | SHAPE;
} }
bool isChunk() const { return (w & TAG) == CHUNK; }
KidsChunk *toChunk() const {
JS_ASSERT(isChunk());
return reinterpret_cast<KidsChunk *>(w & ~jsuword(TAG));
}
void setChunk(KidsChunk *chunk) {
JS_ASSERT(chunk);
JS_ASSERT((reinterpret_cast<jsuword>(chunk) & TAG) == 0);
w = reinterpret_cast<jsuword>(chunk) | CHUNK;
}
bool isHash() const { return (w & TAG) == HASH; } bool isHash() const { return (w & TAG) == HASH; }
KidsHash *toHash() const { KidsHash *toHash() const {
JS_ASSERT(isHash()); JS_ASSERT(isHash());
@ -127,24 +101,35 @@ class PropertyTree
{ {
friend struct ::JSFunction; friend struct ::JSFunction;
JSArenaPool arenaPool; JSCompartment *compartment;
js::Shape *freeList; JSArenaPool arenaPool;
js::Shape *freeList;
bool insertChild(JSContext *cx, js::Shape *parent, js::Shape *child); bool insertChild(JSContext *cx, js::Shape *parent, js::Shape *child);
void removeChild(JSContext *cx, js::Shape *child); void removeChild(js::Shape *child);
PropertyTree();
public: public:
enum { MAX_HEIGHT = 64 }; enum { MAX_HEIGHT = 64 };
PropertyTree(JSCompartment *comp)
: compartment(comp), freeList(NULL)
{
PodZero(&arenaPool);
}
bool init(); bool init();
void finish(); void finish();
js::Shape *newShape(JSContext *cx, bool gcLocked = false); js::Shape *newShapeUnchecked();
js::Shape *newShape(JSContext *cx);
js::Shape *getChild(JSContext *cx, js::Shape *parent, const js::Shape &child); js::Shape *getChild(JSContext *cx, js::Shape *parent, const js::Shape &child);
static void orphanKids(JSContext *cx, js::Shape *shape); void orphanChildren(js::Shape *shape);
static void sweepShapes(JSContext *cx); void sweepShapes(JSContext *cx);
static void unmarkShapes(JSContext *cx); bool checkShapesAllUnmarked(JSContext *cx);
void dumpShapes(JSContext *cx);
#ifdef DEBUG #ifdef DEBUG
static void meter(JSBasicStats *bs, js::Shape *node); static void meter(JSBasicStats *bs, js::Shape *node);
#endif #endif

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

@ -71,12 +71,10 @@ using namespace js;
using namespace js::gc; using namespace js::gc;
uint32 uint32
js_GenerateShape(JSContext *cx, bool gcLocked) js_GenerateShape(JSRuntime *rt)
{ {
JSRuntime *rt;
uint32 shape; uint32 shape;
rt = cx->runtime;
shape = JS_ATOMIC_INCREMENT(&rt->shapeGen); shape = JS_ATOMIC_INCREMENT(&rt->shapeGen);
JS_ASSERT(shape != 0); JS_ASSERT(shape != 0);
if (shape >= SHAPE_OVERFLOW_BIT) { if (shape >= SHAPE_OVERFLOW_BIT) {
@ -90,13 +88,19 @@ js_GenerateShape(JSContext *cx, bool gcLocked)
shape = SHAPE_OVERFLOW_BIT; shape = SHAPE_OVERFLOW_BIT;
#ifdef JS_THREADSAFE #ifdef JS_THREADSAFE
Conditionally<AutoLockGC> lockIf(!gcLocked, rt); AutoLockGC lockIf(rt);
#endif #endif
TriggerGC(rt); TriggerGC(rt);
} }
return shape; return shape;
} }
uint32
js_GenerateShape(JSContext *cx)
{
return js_GenerateShape(cx->runtime);
}
bool bool
JSObject::ensureClassReservedSlotsForEmptyObject(JSContext *cx) JSObject::ensureClassReservedSlotsForEmptyObject(JSContext *cx)
{ {
@ -198,11 +202,10 @@ Shape::hashify(JSRuntime *rt)
#endif #endif
static inline bool static inline bool
InitField(JSContext *cx, EmptyShape *JSRuntime:: *field, Class *clasp, uint32 shape) InitField(JSCompartment *comp, EmptyShape *JSCompartment:: *field, Class *clasp)
{ {
if (EmptyShape *emptyShape = EmptyShape::create(cx, clasp)) { if (EmptyShape *emptyShape = EmptyShape::create(comp, clasp)) {
cx->runtime->*field = emptyShape; comp->*field = emptyShape;
JS_ASSERT(emptyShape->shape == shape);
return true; return true;
} }
return false; return false;
@ -210,7 +213,7 @@ InitField(JSContext *cx, EmptyShape *JSRuntime:: *field, Class *clasp, uint32 sh
/* static */ /* static */
bool bool
Shape::initRuntimeState(JSContext *cx) Shape::initEmptyShapes(JSCompartment *comp)
{ {
/* /*
* NewArguments allocates dslots to have enough room for the argc of the * NewArguments allocates dslots to have enough room for the argc of the
@ -222,12 +225,10 @@ Shape::initRuntimeState(JSContext *cx)
* arguments objects. This helps ensure that any arguments object needing * arguments objects. This helps ensure that any arguments object needing
* its own mutable scope (with unique shape) is a rare event. * its own mutable scope (with unique shape) is a rare event.
*/ */
if (!InitField(cx, &JSRuntime::emptyArgumentsShape, &js_ArgumentsClass, if (!InitField(comp, &JSCompartment::emptyArgumentsShape, &js_ArgumentsClass))
Shape::EMPTY_ARGUMENTS_SHAPE)) {
return false; return false;
}
if (!InitField(cx, &JSRuntime::emptyBlockShape, &js_BlockClass, Shape::EMPTY_BLOCK_SHAPE)) if (!InitField(comp, &JSCompartment::emptyBlockShape, &js_BlockClass))
return false; return false;
/* /*
@ -235,23 +236,19 @@ Shape::initRuntimeState(JSContext *cx)
* and vars do not force the creation of a mutable scope for the particular * and vars do not force the creation of a mutable scope for the particular
* call object being accessed. * call object being accessed.
*/ */
if (!InitField(cx, &JSRuntime::emptyCallShape, &js_CallClass, Shape::EMPTY_CALL_SHAPE)) if (!InitField(comp, &JSCompartment::emptyCallShape, &js_CallClass))
return false; return false;
/* A DeclEnv object holds the name binding for a named function expression. */ /* A DeclEnv object holds the name binding for a named function expression. */
if (!InitField(cx, &JSRuntime::emptyDeclEnvShape, &js_DeclEnvClass, if (!InitField(comp, &JSCompartment::emptyDeclEnvShape, &js_DeclEnvClass))
Shape::EMPTY_DECL_ENV_SHAPE)) {
return false; return false;
}
/* Non-escaping native enumerator objects share this empty scope. */ /* Non-escaping native enumerator objects share this empty scope. */
if (!InitField(cx, &JSRuntime::emptyEnumeratorShape, &js_IteratorClass, if (!InitField(comp, &JSCompartment::emptyEnumeratorShape, &js_IteratorClass))
Shape::EMPTY_ENUMERATOR_SHAPE)) {
return false; return false;
}
/* Same drill for With objects. */ /* Same drill for With objects. */
if (!InitField(cx, &JSRuntime::emptyWithShape, &js_WithClass, Shape::EMPTY_WITH_SHAPE)) if (!InitField(comp, &JSCompartment::emptyWithShape, &js_WithClass))
return false; return false;
return true; return true;
@ -259,16 +256,14 @@ Shape::initRuntimeState(JSContext *cx)
/* static */ /* static */
void void
Shape::finishRuntimeState(JSContext *cx) Shape::finishEmptyShapes(JSCompartment *comp)
{ {
JSRuntime *rt = cx->runtime; comp->emptyArgumentsShape = NULL;
comp->emptyBlockShape = NULL;
rt->emptyArgumentsShape = NULL; comp->emptyCallShape = NULL;
rt->emptyBlockShape = NULL; comp->emptyDeclEnvShape = NULL;
rt->emptyCallShape = NULL; comp->emptyEnumeratorShape = NULL;
rt->emptyDeclEnvShape = NULL; comp->emptyWithShape = NULL;
rt->emptyEnumeratorShape = NULL;
rt->emptyWithShape = NULL;
} }
JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4); JS_STATIC_ASSERT(sizeof(JSHashNumber) == 4);
@ -591,12 +586,12 @@ Shape::newDictionaryShape(JSContext *cx, const Shape &child, Shape **listp)
new (dprop) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs, new (dprop) Shape(child.id, child.rawGetter, child.rawSetter, child.slot, child.attrs,
(child.flags & ~FROZEN) | IN_DICTIONARY, child.shortid, (child.flags & ~FROZEN) | IN_DICTIONARY, child.shortid,
js_GenerateShape(cx, false), child.slotSpan); js_GenerateShape(cx), child.slotSpan);
dprop->listp = NULL; dprop->listp = NULL;
dprop->insertIntoDictionary(listp); dprop->insertIntoDictionary(listp);
JS_RUNTIME_METER(cx->runtime, liveDictModeNodes); JS_COMPARTMENT_METER(cx->compartment->liveDictModeNodes++);
return dprop; return dprop;
} }
@ -613,7 +608,7 @@ Shape::newDictionaryShapeForAddProperty(JSContext *cx, jsid id,
shape->parent = NULL; shape->parent = NULL;
shape->listp = NULL; shape->listp = NULL;
JS_RUNTIME_METER(cx->runtime, liveDictModeNodes); JS_COMPARTMENT_METER(cx->compartment->liveDictModeNodes++);
return shape; return shape;
} }
@ -758,12 +753,6 @@ JSObject::checkShapeConsistency()
} }
prev = shape; prev = shape;
} }
if (throttle == 0) {
JS_ASSERT(!shape->table);
JS_ASSERT(JSID_IS_EMPTY(shape->id));
JS_ASSERT(shape->slot == SHAPE_INVALID_SLOT);
}
} }
} }
#else #else
@ -853,7 +842,6 @@ JSObject::addPropertyInternal(JSContext *cx, jsid id,
} }
#ifdef DEBUG #ifdef DEBUG
LIVE_SCOPE_METER(cx, ++cx->runtime->liveObjectProps); LIVE_SCOPE_METER(cx, ++cx->runtime->liveObjectProps);
JS_RUNTIME_METER(cx->runtime, totalObjectProps);
#endif #endif
CHECK_SHAPE_CONSISTENCY(this); CHECK_SHAPE_CONSISTENCY(this);
METER(adds); METER(adds);
@ -990,7 +978,7 @@ JSObject::putProperty(JSContext *cx, jsid id,
* we regenerate only lastProp->shape. We will clearOwnShape(), which * we regenerate only lastProp->shape. We will clearOwnShape(), which
* sets objShape to lastProp->shape. * sets objShape to lastProp->shape.
*/ */
lastProp->shape = js_GenerateShape(cx, false); lastProp->shape = js_GenerateShape(cx);
clearOwnShape(); clearOwnShape();
} else { } else {
/* /*
@ -1101,7 +1089,7 @@ JSObject::changeProperty(JSContext *cx, const Shape *shape, uintN attrs, uintN m
updateFlags(shape); updateFlags(shape);
/* See the corresponding code in putProperty. */ /* See the corresponding code in putProperty. */
lastProp->shape = js_GenerateShape(cx, false); lastProp->shape = js_GenerateShape(cx);
clearOwnShape(); clearOwnShape();
if (!js_UpdateWatchpointsForShape(cx, this, shape)) { if (!js_UpdateWatchpointsForShape(cx, this, shape)) {
@ -1340,7 +1328,7 @@ JSObject::generateOwnShape(JSContext *cx)
tr->forgetGuardedShapesForObject(this); tr->forgetGuardedShapesForObject(this);
#endif #endif
setOwnShape(js_GenerateShape(cx, false)); setOwnShape(js_GenerateShape(cx));
} }
void void
@ -1479,6 +1467,9 @@ PrintPropertyMethod(JSTracer *trc, char *buf, size_t bufsize)
void void
Shape::trace(JSTracer *trc) const Shape::trace(JSTracer *trc) const
{ {
JSRuntime *rt = trc->context->runtime;
JS_ASSERT_IF(rt->gcCurrentCompartment, compartment == rt->gcCurrentCompartment);
if (IS_GC_MARKING_TRACER(trc)) if (IS_GC_MARKING_TRACER(trc))
mark(); mark();

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

@ -306,21 +306,15 @@ struct Shape : public JSObjectMap
public: public:
inline void freeTable(JSContext *cx); inline void freeTable(JSContext *cx);
static bool initRuntimeState(JSContext *cx); static bool initEmptyShapes(JSCompartment *comp);
static void finishRuntimeState(JSContext *cx); static void finishEmptyShapes(JSCompartment *comp);
enum {
EMPTY_ARGUMENTS_SHAPE = 1,
EMPTY_BLOCK_SHAPE = 2,
EMPTY_CALL_SHAPE = 3,
EMPTY_DECL_ENV_SHAPE = 4,
EMPTY_ENUMERATOR_SHAPE = 5,
EMPTY_WITH_SHAPE = 6,
LAST_RESERVED_SHAPE = 6
};
jsid id; jsid id;
#ifdef DEBUG
JSCompartment *compartment;
#endif
protected: protected:
union { union {
js::PropertyOp rawGetter; /* getter and setter hooks or objects */ js::PropertyOp rawGetter; /* getter and setter hooks or objects */
@ -510,7 +504,7 @@ struct Shape : public JSObjectMap
uintN flags, intN shortid, uint32 shape = INVALID_SHAPE, uint32 slotSpan = 0); uintN flags, intN shortid, uint32 shape = INVALID_SHAPE, uint32 slotSpan = 0);
/* Used by EmptyShape (see jsscopeinlines.h). */ /* Used by EmptyShape (see jsscopeinlines.h). */
Shape(JSContext *cx, Class *aclasp); Shape(JSCompartment *comp, Class *aclasp);
bool marked() const { return (flags & MARK) != 0; } bool marked() const { return (flags & MARK) != 0; }
void mark() const { flags |= MARK; } void mark() const { flags |= MARK; }
@ -638,15 +632,22 @@ struct Shape : public JSObjectMap
struct EmptyShape : public js::Shape struct EmptyShape : public js::Shape
{ {
EmptyShape(JSContext *cx, js::Class *aclasp); EmptyShape(JSCompartment *comp, js::Class *aclasp);
js::Class *getClass() const { return clasp; }; js::Class *getClass() const { return clasp; };
static EmptyShape *create(JSCompartment *comp, js::Class *clasp) {
js::Shape *eprop = comp->propertyTree.newShapeUnchecked();
if (!eprop)
return NULL;
return new (eprop) EmptyShape(comp, clasp);
}
static EmptyShape *create(JSContext *cx, js::Class *clasp) { static EmptyShape *create(JSContext *cx, js::Class *clasp) {
js::Shape *eprop = JS_PROPERTY_TREE(cx).newShape(cx); js::Shape *eprop = JS_PROPERTY_TREE(cx).newShape(cx);
if (!eprop) if (!eprop)
return NULL; return NULL;
return new (eprop) EmptyShape(cx, clasp); return new (eprop) EmptyShape(cx->compartment, clasp);
} }
}; };
@ -737,6 +738,7 @@ JSObject::setLastProperty(const js::Shape *shape)
JS_ASSERT(!inDictionaryMode()); JS_ASSERT(!inDictionaryMode());
JS_ASSERT(!JSID_IS_VOID(shape->id)); JS_ASSERT(!JSID_IS_VOID(shape->id));
JS_ASSERT_IF(lastProp, !JSID_IS_VOID(lastProp->id)); JS_ASSERT_IF(lastProp, !JSID_IS_VOID(lastProp->id));
JS_ASSERT(shape->compartment == compartment());
lastProp = const_cast<js::Shape *>(shape); lastProp = const_cast<js::Shape *>(shape);
} }
@ -805,12 +807,11 @@ Shape::insertIntoDictionary(js::Shape **dictp)
((shape)->hasShortID() ? INT_TO_JSID((shape)->shortid) \ ((shape)->hasShortID() ? INT_TO_JSID((shape)->shortid) \
: (shape)->id) : (shape)->id)
#ifndef JS_THREADSAFE extern uint32
# define js_GenerateShape(cx, gcLocked) js_GenerateShape (cx) js_GenerateShape(JSRuntime *rt);
#endif
extern uint32 extern uint32
js_GenerateShape(JSContext *cx, bool gcLocked); js_GenerateShape(JSContext *cx);
#ifdef DEBUG #ifdef DEBUG
struct JSScopeStats { struct JSScopeStats {

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

@ -107,7 +107,7 @@ JSObject::updateShape(JSContext *cx)
JS_ASSERT(isNative()); JS_ASSERT(isNative());
js::LeaveTraceIfGlobalObject(cx, this); js::LeaveTraceIfGlobalObject(cx, this);
if (hasOwnShape()) if (hasOwnShape())
setOwnShape(js_GenerateShape(cx, false)); setOwnShape(js_GenerateShape(cx));
else else
objShape = lastProp->shape; objShape = lastProp->shape;
} }
@ -146,13 +146,13 @@ JSObject::trace(JSTracer *trc)
* it must have the same shape as lastProp. * it must have the same shape as lastProp.
*/ */
if (!shape->hasRegenFlag()) { if (!shape->hasRegenFlag()) {
shape->shape = js_RegenerateShapeForGC(cx); shape->shape = js_RegenerateShapeForGC(cx->runtime);
shape->setRegenFlag(); shape->setRegenFlag();
} }
uint32 newShape = shape->shape; uint32 newShape = shape->shape;
if (hasOwnShape()) { if (hasOwnShape()) {
newShape = js_RegenerateShapeForGC(cx); newShape = js_RegenerateShapeForGC(cx->runtime);
JS_ASSERT(newShape != shape->shape); JS_ASSERT(newShape != shape->shape);
} }
objShape = newShape; objShape = newShape;
@ -180,10 +180,18 @@ Shape::Shape(jsid id, js::PropertyOp getter, js::PropertyOp setter, uint32 slot,
} }
inline inline
Shape::Shape(JSContext *cx, Class *aclasp) Shape::Shape(JSCompartment *comp, Class *aclasp)
: JSObjectMap(js_GenerateShape(cx, false), JSSLOT_FREE(aclasp)), numSearches(0), table(NULL), : JSObjectMap(js_GenerateShape(comp->rt), JSSLOT_FREE(aclasp)),
id(JSID_EMPTY), clasp(aclasp), rawSetter(NULL), slot(SHAPE_INVALID_SLOT), attrs(0), numSearches(0),
flags(SHARED_EMPTY), shortid(0), parent(NULL) table(NULL),
id(JSID_EMPTY),
clasp(aclasp),
rawSetter(NULL),
slot(SHAPE_INVALID_SLOT),
attrs(0),
flags(SHARED_EMPTY),
shortid(0),
parent(NULL)
{ {
kids.setNull(); kids.setNull();
} }
@ -276,12 +284,12 @@ Shape::set(JSContext* cx, JSObject* obj, js::Value* vp) const
} }
inline inline
EmptyShape::EmptyShape(JSContext *cx, js::Class *aclasp) EmptyShape::EmptyShape(JSCompartment *comp, js::Class *aclasp)
: js::Shape(cx, aclasp) : js::Shape(comp, aclasp)
{ {
#ifdef DEBUG #ifdef DEBUG
if (cx->runtime->meterEmptyShapes()) if (comp->rt->meterEmptyShapes())
cx->runtime->emptyShapes.put(this); comp->emptyShapes.put(this);
#endif #endif
} }

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

@ -52,14 +52,14 @@ namespace js {
inline inline
Bindings::Bindings(JSContext *cx) Bindings::Bindings(JSContext *cx)
: lastBinding(cx->runtime->emptyCallShape), nargs(0), nvars(0), nupvars(0) : lastBinding(cx->compartment->emptyCallShape), nargs(0), nvars(0), nupvars(0)
{ {
} }
inline void inline void
Bindings::transfer(JSContext *cx, Bindings *bindings) Bindings::transfer(JSContext *cx, Bindings *bindings)
{ {
JS_ASSERT(lastBinding == cx->runtime->emptyCallShape); JS_ASSERT(lastBinding == cx->compartment->emptyCallShape);
*this = *bindings; *this = *bindings;
#ifdef DEBUG #ifdef DEBUG
@ -74,7 +74,7 @@ Bindings::transfer(JSContext *cx, Bindings *bindings)
inline void inline void
Bindings::clone(JSContext *cx, Bindings *bindings) Bindings::clone(JSContext *cx, Bindings *bindings)
{ {
JS_ASSERT(lastBinding == cx->runtime->emptyCallShape); JS_ASSERT(lastBinding == cx->compartment->emptyCallShape);
/* /*
* Non-dictionary bindings are fine to share, as are dictionary bindings if * Non-dictionary bindings are fine to share, as are dictionary bindings if