Sweep compartments when no objects live in them (bug 639270, r=gregor).
authorAndreas Gal <gal@mozilla.com>
Tue, 08 Mar 2011 20:58:38 -0800
changeset 64258 206d6b6b392ff8d315e6fc3daf41d54aa6f429cc
parent 64257 38aba506e3c42ad153bf207143718a254c9d8d7d
child 64259 b16be37906fed4c7e11de6c42cbab6e23345fcd9
push idunknown
push userunknown
push dateunknown
reviewersgregor
bugs639270
milestone2.0b13pre
Sweep compartments when no objects live in them (bug 639270, r=gregor).
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/xpconnect/src/nsXPConnect.cpp
--- 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" */