Sweep compartments when no objects live in them (
bug 639270, r=gregor).
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1328,17 +1328,17 @@ JS_TransplantObject(JSContext *cx, JSObj
}
// Now, iterate through other scopes looking for references to the
// old outer window. They need to be updated to point at the new
// outer window. They also might transition between different
// types of security wrappers based on whether the new compartment
// is same origin with them.
Value targetv = ObjectValue(*obj);
- WrapperVector &vector = cx->runtime->compartments;
+ CompartmentVector &vector = cx->runtime->compartments;
AutoValueVector toTransplant(cx);
if (!toTransplant.reserve(vector.length()))
return NULL;
for (JSCompartment **p = vector.begin(), **end = vector.end(); p != end; ++p) {
WrapperMap &pmap = (*p)->crossCompartmentWrappers;
if (WrapperMap::Ptr wp = pmap.lookup(origv)) {
// We found a wrapper. Remember and root it.
@@ -1422,17 +1422,17 @@ js_TransplantObjectWithWrapper(JSContext
obj = targetwrapper;
}
// Now, iterate through other scopes looking for references to the old
// location object. Note that the entries in the maps are for |origobj|
// and not |origwrapper|. They need to be updated to point at the new
// location object.
Value targetv = ObjectValue(*targetobj);
- WrapperVector &vector = cx->runtime->compartments;
+ CompartmentVector &vector = cx->runtime->compartments;
AutoValueVector toTransplant(cx);
if (!toTransplant.reserve(vector.length()))
return NULL;
for (JSCompartment **p = vector.begin(), **end = vector.end(); p != end; ++p) {
WrapperMap &pmap = (*p)->crossCompartmentWrappers;
if (WrapperMap::Ptr wp = pmap.lookup(origv)) {
// We found a wrapper. Remember and root it.
@@ -3076,24 +3076,43 @@ JS_NewGlobalObject(JSContext *cx, JSClas
!js_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_REGEXP_STATICS, ObjectValue(*res)) ||
!js_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_FLAGS, Int32Value(0))) {
return NULL;
}
return obj;
}
+class AutoHoldCompartment {
+ public:
+ explicit AutoHoldCompartment(JSCompartment *compartment JS_GUARD_OBJECT_NOTIFIER_PARAM)
+ : holdp(&compartment->hold)
+ {
+ JS_GUARD_OBJECT_NOTIFIER_INIT;
+ *holdp = true;
+ }
+
+ ~AutoHoldCompartment() {
+ *holdp = false;
+ }
+ private:
+ bool *holdp;
+ JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
JS_PUBLIC_API(JSObject *)
JS_NewCompartmentAndGlobalObject(JSContext *cx, JSClass *clasp, JSPrincipals *principals)
{
CHECK_REQUEST(cx);
JSCompartment *compartment = NewCompartment(cx, principals);
if (!compartment)
return NULL;
+ AutoHoldCompartment hold(compartment);
+
JSCompartment *saved = cx->compartment;
cx->compartment = compartment;
JSObject *obj = JS_NewGlobalObject(cx, clasp);
cx->compartment = saved;
return obj;
}
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -983,29 +983,29 @@ typedef struct JSPropertyTreeEntry {
js::Shape *child;
} JSPropertyTreeEntry;
typedef void
(* JSActivityCallback)(void *arg, JSBool active);
namespace js {
-typedef js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> WrapperVector;
+typedef js::Vector<JSCompartment *, 0, js::SystemAllocPolicy> CompartmentVector;
}
struct JSRuntime {
/* Default compartment. */
JSCompartment *atomsCompartment;
#ifdef JS_THREADSAFE
bool atomsCompartmentIsLocked;
#endif
/* List of compartments (protected by the GC lock). */
- js::WrapperVector compartments;
+ js::CompartmentVector compartments;
/* Runtime state, synchronized by the stateChange/gcLock condvar/lock. */
JSRuntimeState state;
/* Context create/destroy callback. */
JSContextCallback cxCallback;
/* Compartment create/destroy callback. */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -61,34 +61,34 @@ using namespace js;
using namespace js::gc;
JSCompartment::JSCompartment(JSRuntime *rt)
: rt(rt),
principals(NULL),
gcBytes(0),
gcTriggerBytes(0),
gcLastBytes(0),
+ hold(false),
data(NULL),
active(false),
#ifdef JS_METHODJIT
jaegerCompartment(NULL),
#endif
propertyTree(thisForCtor()),
emptyArgumentsShape(NULL),
emptyBlockShape(NULL),
emptyCallShape(NULL),
emptyDeclEnvShape(NULL),
emptyEnumeratorShape(NULL),
emptyWithShape(NULL),
debugMode(rt->debugMode),
#if ENABLE_YARR_JIT
regExpAllocator(NULL),
#endif
- mathCache(NULL),
- marked(false)
+ mathCache(NULL)
{
JS_INIT_CLIST(&scripts);
#ifdef JS_TRACER
/* InitJIT expects this area to be zero'd. */
PodZero(&traceMonitor);
#endif
@@ -442,45 +442,33 @@ ScriptPoolDestroyed(JSContext *cx, mjit:
}
}
return pool->m_destroy;
}
#endif
/*
* This method marks pointers that cross compartment boundaries. It should be
- * called only by per-compartment GCs, since full GCs naturally follow pointers
+ * called only for per-compartment GCs, since full GCs naturally follow pointers
* across compartments.
*/
void
-JSCompartment::markCrossCompartment(JSTracer *trc)
+JSCompartment::markCrossCompartmentWrappers(JSTracer *trc)
{
+ JS_ASSERT(trc->context->runtime->gcCurrentCompartment);
+
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront())
MarkValue(trc, e.front().key, "cross-compartment wrapper");
}
void
-JSCompartment::mark(JSTracer *trc)
-{
- if (IS_GC_MARKING_TRACER(trc)) {
- JSRuntime *rt = trc->context->runtime;
-
- if (rt->gcCurrentCompartment && rt->gcCurrentCompartment != this)
- return;
-
- if (marked)
- return;
- marked = true;
- }
-}
-
-void
JSCompartment::sweep(JSContext *cx, uint32 releaseInterval)
{
chunk = NULL;
+
/* Remove dead wrappers from the table. */
for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
JS_ASSERT_IF(IsAboutToBeFinalized(cx, e.front().key.toGCThing()) &&
!IsAboutToBeFinalized(cx, e.front().value.toGCThing()),
e.front().key.isString());
if (IsAboutToBeFinalized(cx, e.front().key.toGCThing()) ||
IsAboutToBeFinalized(cx, e.front().value.toGCThing())) {
e.removeFront();
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -376,16 +376,18 @@ struct JS_FRIEND_API(JSCompartment) {
js::gc::ArenaList arenas[js::gc::FINALIZE_LIMIT];
js::gc::FreeLists freeLists;
size_t gcBytes;
size_t gcTriggerBytes;
size_t gcLastBytes;
+ bool hold;
+
#ifdef JS_GCMETER
js::gc::JSGCArenaStats compartmentStats[js::gc::FINALIZE_LIMIT];
#endif
#ifdef JS_TRACER
/* Trace-tree JIT recorder/interpreter state. */
js::TraceMonitor traceMonitor;
#endif
@@ -446,20 +448,17 @@ struct JS_FRIEND_API(JSCompartment) {
LazyToSourceCache toSourceCache;
JSCompartment(JSRuntime *rt);
~JSCompartment();
bool init();
/* Mark cross-compartment wrappers. */
- void markCrossCompartment(JSTracer *trc);
-
- /* Mark this compartment's local roots. */
- void mark(JSTracer *trc);
+ void markCrossCompartmentWrappers(JSTracer *trc);
bool wrap(JSContext *cx, js::Value *vp);
bool wrap(JSContext *cx, JSString **strp);
bool wrap(JSContext *cx, JSObject **objp);
bool wrapId(JSContext *cx, jsid *idp);
bool wrap(JSContext *cx, js::PropertyOp *op);
bool wrap(JSContext *cx, js::StrictPropertyOp *op);
bool wrap(JSContext *cx, js::PropertyDescriptor *desc);
@@ -476,34 +475,29 @@ struct JS_FRIEND_API(JSCompartment) {
js::DtoaCache dtoaCache;
private:
js::MathCache *mathCache;
js::MathCache *allocMathCache(JSContext *cx);
- bool marked;
-
typedef js::HashMap<jsbytecode*,
size_t,
js::DefaultHasher<jsbytecode*>,
js::SystemAllocPolicy> BackEdgeMap;
BackEdgeMap backEdgeTable;
JSCompartment *thisForCtor() { return this; }
public:
js::MathCache *getMathCache(JSContext *cx) {
return mathCache ? mathCache : allocMathCache(cx);
}
- bool isMarked() { return marked; }
- void clearMark() { marked = false; }
-
size_t backEdgeCount(jsbytecode *pc) const;
size_t incBackEdgeCount(jsbytecode *pc);
};
#define JS_SCRIPTS_TO_GC(cx) ((cx)->compartment->scriptsToGC)
#define JS_PROPERTY_TREE(cx) ((cx)->compartment->propertyTree)
#ifdef DEBUG
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1628,19 +1628,16 @@ MarkContext(JSTracer *trc, JSContext *ac
for (js::AutoGCRooter *gcr = acx->autoGCRooters; gcr; gcr = gcr->down)
gcr->trace(trc);
if (acx->sharpObjectMap.depth > 0)
js_TraceSharpMap(trc, &acx->sharpObjectMap);
MarkValue(trc, acx->iterValue, "iterValue");
-
- if (acx->compartment)
- acx->compartment->mark(trc);
}
JS_REQUIRES_STACK void
MarkRuntime(JSTracer *trc)
{
JSRuntime *rt = trc->context->runtime;
if (rt->state != JSRTS_LANDING)
@@ -1719,18 +1716,16 @@ MarkRuntime(JSTracer *trc)
js_TraceAtomState(trc);
js_MarkTraps(trc);
iter = NULL;
while (JSContext *acx = js_ContextIterator(rt, JS_TRUE, &iter))
MarkContext(trc, acx);
- rt->atomsCompartment->mark(trc);
-
#ifdef JS_TRACER
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
(*c)->traceMonitor.mark(trc);
#endif
for (ThreadDataIter i(rt); !i.empty(); i.popFront())
i.threadData()->mark(trc);
@@ -2182,23 +2177,18 @@ SweepCompartments(JSContext *cx, JSGCInv
JSCompartment **end = rt->compartments.end();
JSCompartment **write = read;
JS_ASSERT(rt->compartments.length() >= 1);
JS_ASSERT(*rt->compartments.begin() == rt->atomsCompartment);
while (read < end) {
JSCompartment *compartment = *read++;
- /*
- * Unmarked compartments containing marked objects don't get deleted,
- * except when LAST_CONTEXT GC is performed.
- */
- if ((!compartment->isMarked() && compartment->arenaListsAreEmpty()) ||
- gckind == GC_LAST_CONTEXT)
- {
+ if (!compartment->hold &&
+ (compartment->arenaListsAreEmpty() || gckind == GC_LAST_CONTEXT)) {
JS_ASSERT(compartment->freeLists.isEmpty());
if (callback)
(void) callback(cx, compartment, JSCOMPARTMENT_DESTROY);
if (compartment->principals)
JSPRINCIPALS_DROP(cx, compartment->principals);
js_delete(compartment);
continue;
}
@@ -2263,35 +2253,32 @@ PreGCCleanup(JSContext *cx, JSGCInvocati
static void
MarkAndSweepCompartment(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIMER_PARAM)
{
JSRuntime *rt = cx->runtime;
rt->gcNumber++;
JS_ASSERT(!rt->gcRegenShapes);
JS_ASSERT(gckind != GC_LAST_CONTEXT);
JS_ASSERT(comp != rt->atomsCompartment);
- JS_ASSERT(!comp->isMarked());
JS_ASSERT(comp->rt->gcMode == JSGC_MODE_COMPARTMENT);
/*
* Mark phase.
*/
GCMarker gcmarker(cx);
JS_ASSERT(IS_GC_MARKING_TRACER(&gcmarker));
JS_ASSERT(gcmarker.getMarkColor() == BLACK);
rt->gcMarkingTracer = &gcmarker;
gcmarker.stackLimit = cx->stackLimit;
for (GCChunkSet::Range r(rt->gcChunkSet.all()); !r.empty(); r.popFront())
r.front()->clearMarkBitmap();
for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
- (*c)->markCrossCompartment(&gcmarker);
-
- comp->mark(&gcmarker);
+ (*c)->markCrossCompartmentWrappers(&gcmarker);
MarkRuntime(&gcmarker);
/*
* Mark children of things that caused too deep recursion during the above
* tracing.
*/
gcmarker.markDelayedChildren();
@@ -2379,18 +2366,16 @@ MarkAndSweepCompartment(JSContext *cx, J
/*
* Destroy arenas after we finished the sweeping so finalizers can safely
* use js_IsAboutToBeFinalized().
*/
ExpireGCChunks(rt);
TIMESTAMP(sweepDestroyEnd);
- comp->clearMark();
-
if (rt->gcCallback)
(void) rt->gcCallback(cx, JSGC_FINALIZE_END);
}
/*
* Perform mark-and-sweep GC.
*
* In a JS_THREADSAFE build, the calling thread must be rt->gcThread and each
@@ -2518,19 +2503,16 @@ MarkAndSweep(JSContext *cx, JSGCInvocati
/*
* Destroy arenas after we finished the sweeping so finalizers can safely
* use js_IsAboutToBeFinalized().
*/
ExpireGCChunks(rt);
TIMESTAMP(sweepDestroyEnd);
- for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
- (*c)->clearMark();
-
if (rt->gcCallback)
(void) rt->gcCallback(cx, JSGC_FINALIZE_END);
#ifdef DEBUG_srcnotesize
{ extern void DumpSrcNoteSizeHist();
DumpSrcNoteSizeHist();
printf("GC HEAP SIZE %lu\n", (unsigned long)rt->gcBytes);
}
#endif
@@ -2731,19 +2713,16 @@ GCUntilDone(JSContext *cx, JSCompartment
LetOtherGCFinish(cx);
}
#endif
return;
}
AutoGCSession gcsession(cx);
- for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
- JS_ASSERT(!(*c)->isMarked());
-
/*
* 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);
rt->gcCurrentCompartment = comp;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -6504,21 +6504,16 @@ js_TraceObject(JSTracer *trc, JSObject *
}
#ifdef JS_DUMP_SCOPE_METERS
MeterEntryCount(obj->propertyCount);
#endif
obj->trace(trc);
- if (obj->getClass()->flags & JSCLASS_IS_GLOBAL) {
- JSCompartment *compartment = obj->getCompartment();
- compartment->mark(trc);
- }
-
/*
* NB: clasp->mark could mutate something (which would be a bug, but we are
* defensive), so don't hoist this above calling clasp->mark.
*/
uint32 nslots = Min(obj->numSlots(), obj->slotSpan());
for (uint32 i = 0; i != nslots; ++i) {
const Value &v = obj->getSlot(i);
JS_SET_TRACING_DETAILS(trc, js_PrintObjectSlotName, obj, i);
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -2556,17 +2556,17 @@ nsXPConnect::CheckForDebugMode(JSRuntime
{
struct AutoDestroyContext {
JSContext *cx;
AutoDestroyContext(JSContext *cx) : cx(cx) {}
~AutoDestroyContext() { JS_DestroyContext(cx); }
} adc(cx);
JSAutoRequest ar(cx);
- js::WrapperVector &vector = rt->compartments;
+ js::CompartmentVector &vector = rt->compartments;
for (JSCompartment **p = vector.begin(); p != vector.end(); ++p) {
JSCompartment *comp = *p;
if (!comp->principals) {
/* Ignore special compartments (atoms, JSD compartments) */
continue;
}
/* ParticipatesInCycleCollection means "on the main thread" */