Bug 650978 - Add gczeal setting to GC on every allocation (r=gwagner)
authorBill McCloskey <wmccloskey@mozilla.com>
Wed, 01 Jun 2011 17:48:52 -0700
changeset 70643 b36861bd7a01dabcf875f11e1e2ae50a0b370f31
parent 70642 596dca744a3768eba04cab3e3dbe186d39f10117
child 70644 b0d93728d58c7021edab3a2f08bd7fbbc3177b6f
push idunknown
push userunknown
push dateunknown
reviewersgwagner
bugs650978
milestone6.0a1
Bug 650978 - Add gczeal setting to GC on every allocation (r=gwagner)
dom/base/nsJSEnvironment.cpp
ipc/testshell/XPCShellEnvironment.cpp
js/jetpack/JetpackChild.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.h
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jsobj.cpp
js/src/shell/js.cpp
js/src/xpconnect/shell/xpcshell.cpp
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1008,17 +1008,17 @@ nsJSContext::JSOptionChangedCallback(con
   ::JS_SetOptions(context->mContext, newDefaultJSOptions & JSRUNOPTION_MASK);
 
   // Save the new defaults for the next page load (InitContext).
   context->mDefaultJSOptions = newDefaultJSOptions;
 
 #ifdef JS_GC_ZEAL
   PRInt32 zeal = nsContentUtils::GetIntPref(js_zeal_option_str, -1);
   if (zeal >= 0)
-    ::JS_SetGCZeal(context->mContext, (PRUint8)zeal);
+    ::JS_SetGCZeal(context->mContext, (PRUint8)zeal, JS_DEFAULT_ZEAL_FREQ, JS_FALSE);
 #endif
 
   return 0;
 }
 
 nsJSContext::nsJSContext(JSRuntime *aRuntime)
   : mGCOnDestruction(PR_TRUE),
     mExecuteDepth(0)
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -432,17 +432,17 @@ GCZeal(JSContext *cx,
        jsval *vp)
 {
   jsval* argv = JS_ARGV(cx, vp);
 
   uint32 zeal;
   if (!JS_ValueToECMAUint32(cx, argv[0], &zeal))
     return JS_FALSE;
 
-  JS_SetGCZeal(cx, PRUint8(zeal));
+  JS_SetGCZeal(cx, PRUint8(zeal), JS_DEFAULT_ZEAL_FREQ, JS_FALSE);
   return JS_TRUE;
 }
 #endif
 
 #ifdef DEBUG
 
 static JSBool
 DumpHeap(JSContext *cx,
--- a/js/jetpack/JetpackChild.cpp
+++ b/js/jetpack/JetpackChild.cpp
@@ -569,17 +569,17 @@ JSBool
 JetpackChild::GCZeal(JSContext* cx, uintN argc, jsval *vp)
 {
   jsval* argv = JS_ARGV(cx, vp);
 
   uint32 zeal;
   if (!JS_ValueToECMAUint32(cx, argv[0], &zeal))
     return JS_FALSE;
 
-  JS_SetGCZeal(cx, PRUint8(zeal));
+  JS_SetGCZeal(cx, PRUint8(zeal), JS_DEFAULT_ZEAL_FREQ, JS_FALSE);
   return JS_TRUE;
 }
 #endif
 
 JSBool
 JetpackChild::NoteIntentionalCrash(JSContext* cx, uintN argc, jsval *vp)
 {
   mozilla::NoteIntentionalCrash("jetpack");
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2586,24 +2586,34 @@ JS_DumpHeap(JSContext *cx, FILE *fp, voi
 
 extern JS_PUBLIC_API(JSBool)
 JS_IsGCMarkingTracer(JSTracer *trc)
 {
     return IS_GC_MARKING_TRACER(trc);
 }
 
 JS_PUBLIC_API(void)
-JS_GC(JSContext *cx)
-{
+JS_CompartmentGC(JSContext *cx, JSCompartment *comp)
+{
+    /* We cannot GC the atoms compartment alone; use a full GC instead. */
+    JS_ASSERT(comp != cx->runtime->atomsCompartment);
+
     LeaveTrace(cx);
 
     /* Don't nuke active arenas if executing or compiling. */
     if (cx->tempPool.current == &cx->tempPool.first)
         JS_FinishArenaPool(&cx->tempPool);
-    js_GC(cx, NULL, GC_NORMAL);
+
+    js_GC(cx, comp, GC_NORMAL);
+}
+
+JS_PUBLIC_API(void)
+JS_GC(JSContext *cx)
+{
+    JS_CompartmentGC(cx, NULL);
 }
 
 JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext *cx)
 {
     LeaveTrace(cx);
 
     /* Don't nuke active arenas if executing or compiling. */
@@ -6117,19 +6127,29 @@ JS_ClearContextThread(JSContext *cx)
     return reinterpret_cast<jsword>(t->id);
 #else
     return 0;
 #endif
 }
 
 #ifdef JS_GC_ZEAL
 JS_PUBLIC_API(void)
-JS_SetGCZeal(JSContext *cx, uint8 zeal)
-{
-    cx->runtime->gcZeal = zeal;
+JS_SetGCZeal(JSContext *cx, uint8 zeal, uint32 frequency, JSBool compartment)
+{
+    cx->runtime->gcZeal_ = zeal;
+    cx->runtime->gcZealFrequency = frequency;
+    cx->runtime->gcNextScheduled = frequency;
+    cx->runtime->gcDebugCompartmentGC = !!compartment;
+}
+
+JS_PUBLIC_API(void)
+JS_ScheduleGC(JSContext *cx, uint32 count, JSBool compartment)
+{
+    cx->runtime->gcNextScheduled = count;
+    cx->runtime->gcDebugCompartmentGC = !!compartment;
 }
 #endif
 
 /************************************************************************/
 
 #if !defined(STATIC_EXPORTABLE_JS_API) && !defined(STATIC_JS_API) && defined(XP_WIN)
 
 #include "jswin.h"
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1762,16 +1762,19 @@ JS_DumpHeap(JSContext *cx, FILE *fp, voi
 
 /*
  * Garbage collector API.
  */
 extern JS_PUBLIC_API(void)
 JS_GC(JSContext *cx);
 
 extern JS_PUBLIC_API(void)
+JS_CompartmentGC(JSContext *cx, JSCompartment *comp);
+
+extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext *cx);
 
 extern JS_PUBLIC_API(JSGCCallback)
 JS_SetGCCallback(JSContext *cx, JSGCCallback cb);
 
 extern JS_PUBLIC_API(JSGCCallback)
 JS_SetGCCallbackRT(JSRuntime *rt, JSGCCallback cb);
 
@@ -3859,15 +3862,20 @@ JS_NewObjectForConstructor(JSContext *cx
 
 /************************************************************************/
 
 #ifdef DEBUG
 #define JS_GC_ZEAL 1
 #endif
 
 #ifdef JS_GC_ZEAL
+#define JS_DEFAULT_ZEAL_FREQ 100
+
 extern JS_PUBLIC_API(void)
-JS_SetGCZeal(JSContext *cx, uint8 zeal);
+JS_SetGCZeal(JSContext *cx, uint8 zeal, uint32 frequency, JSBool compartment);
+
+extern JS_PUBLIC_API(void)
+JS_ScheduleGC(JSContext *cx, uint32 count, JSBool compartment);
 #endif
 
 JS_END_EXTERN_C
 
 #endif /* jsapi_h___ */
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -446,18 +446,53 @@ struct JSRuntime {
      * thread may be lost due to unsynchronized read-modify-write cycles on
      * other threads.
      */
     bool                gcPoke;
     bool                gcMarkAndSweep;
     bool                gcRunning;
     bool                gcRegenShapes;
 
+    /*
+     * These options control the zealousness of the GC. The fundamental values
+     * are gcNextScheduled and gcDebugCompartmentGC. At every allocation,
+     * gcNextScheduled is decremented. When it reaches zero, we do either a
+     * full or a compartmental GC, based on gcDebugCompartmentGC.
+     *
+     * At this point, if gcZeal_ >= 2 then gcNextScheduled is reset to the
+     * value of gcZealFrequency. Otherwise, no additional GCs take place.
+     *
+     * You can control these values in several ways:
+     *   - Pass the -Z flag to the shell (see the usage info for details)
+     *   - Call gczeal() or schedulegc() from inside shell-executed JS code
+     *     (see the help for details)
+     *
+     * Additionally, if gzZeal_ == 1 then we perform GCs in select places
+     * (during MaybeGC and whenever a GC poke happens). This option is mainly
+     * useful to embedders.
+     */
 #ifdef JS_GC_ZEAL
-    jsrefcount          gcZeal;
+    int                 gcZeal_;
+    int                 gcZealFrequency;
+    int                 gcNextScheduled;
+    bool                gcDebugCompartmentGC;
+
+    int gcZeal() { return gcZeal_; }
+
+    bool needZealousGC() {
+        if (gcNextScheduled > 0 && --gcNextScheduled == 0) {
+            if (gcZeal() >= 2)
+                gcNextScheduled = gcZealFrequency;
+            return true;
+        }
+        return false;
+    }
+#else
+    int gcZeal() { return 0; }
+    bool needZealousGC() { return false; }
 #endif
 
     JSGCCallback        gcCallback;
 
   private:
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from gcMaxMallocBytes down to zero.
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1225,17 +1225,17 @@ SetCallArg(JSContext *cx, JSObject *obj,
     uintN i = (uint16) JSID_TO_INT(id);
 
     Value *argp;
     if (StackFrame *fp = obj->maybeCallObjStackFrame())
         argp = &fp->formalArg(i);
     else
         argp = &obj->callObjArg(i);
 
-    GC_POKE(cx, *argp);
+    GCPoke(cx, *argp);
     *argp = *vp;
     return true;
 }
 
 JSBool
 GetCallUpvar(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     JS_ASSERT((int16) JSID_TO_INT(id) == JSID_TO_INT(id));
@@ -1248,17 +1248,17 @@ GetCallUpvar(JSContext *cx, JSObject *ob
 JSBool
 SetCallUpvar(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
     JS_ASSERT((int16) JSID_TO_INT(id) == JSID_TO_INT(id));
     uintN i = (uint16) JSID_TO_INT(id);
 
     Value *up = &obj->getCallObjCallee()->getFlatClosureUpvar(i);
 
-    GC_POKE(cx, *up);
+    GCPoke(cx, *up);
     *up = *vp;
     return true;
 }
 
 JSBool
 GetCallVar(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     JS_ASSERT((int16) JSID_TO_INT(id) == JSID_TO_INT(id));
@@ -1305,17 +1305,17 @@ SetCallVar(JSContext *cx, JSObject *obj,
 #endif
 
     Value *varp;
     if (StackFrame *fp = obj->maybeCallObjStackFrame())
         varp = &fp->varSlot(i);
     else
         varp = &obj->callObjVar(i);
 
-    GC_POKE(cx, *varp);
+    GCPoke(cx, *varp);
     *varp = *vp;
     return true;
 }
 
 } // namespace js
 
 #if JS_TRACER
 JSBool JS_FASTCALL
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1320,20 +1320,16 @@ CheckAllocation(JSContext *cx)
     return true;
 }
 #endif
 
 inline bool
 NeedLastDitchGC(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime;
-#ifdef JS_GC_ZEAL
-    if (rt->gcZeal >= 1)
-        return true;
-#endif
     return rt->gcIsNeeded;
 }
 
 /*
  * Return false only if the GC run but could not bring its memory usage under
  * JSRuntime::gcMaxBytes.
  */
 static bool
@@ -1353,33 +1349,39 @@ RunLastDitchGC(JSContext *cx)
 #ifdef JS_THREADSAFE
     if (rt->gcBytes >= rt->gcMaxBytes)
         cx->runtime->gcHelperThread.waitBackgroundSweepEnd(cx->runtime);
 #endif
 
     return rt->gcBytes < rt->gcMaxBytes;
 }
 
+static inline bool
+IsGCAllowed(JSContext *cx)
+{
+    return !JS_ON_TRACE(cx) && !JS_THREAD_DATA(cx)->waiveGCQuota;
+}
+
 template <typename T>
 inline Cell *
 RefillTypedFreeList(JSContext *cx, unsigned thingKind)
 {
     JS_ASSERT(!cx->runtime->gcRunning);
 
     /*
      * For compatibility with older code we tolerate calling the allocator
      * during the GC in optimized builds.
      */
     if (cx->runtime->gcRunning)
         return NULL;
 
     JSCompartment *compartment = cx->compartment;
     JS_ASSERT(!compartment->freeLists.finalizables[thingKind]);
 
-    bool canGC = !JS_ON_TRACE(cx) && !JS_THREAD_DATA(cx)->waiveGCQuota;
+    bool canGC = IsGCAllowed(cx);
     bool runGC = canGC && JS_UNLIKELY(NeedLastDitchGC(cx));
     for (;;) {
         if (runGC) {
             if (!RunLastDitchGC(cx))
                 break;
 
             /*
              * The JSGC_END callback can legitimately allocate new GC
@@ -1913,22 +1915,20 @@ TriggerGC(JSRuntime *rt)
 }
 
 void
 TriggerCompartmentGC(JSCompartment *comp)
 {
     JSRuntime *rt = comp->rt;
     JS_ASSERT(!rt->gcRunning);
 
-#ifdef JS_GC_ZEAL
-    if (rt->gcZeal >= 1) {
+    if (rt->gcZeal()) {
         TriggerGC(rt);
         return;
     }
-#endif
 
     if (rt->gcMode != JSGC_MODE_COMPARTMENT || comp == rt->atomsCompartment) {
         /* We can't do a compartmental GC of the default compartment. */
         TriggerGC(rt);
         return;
     }
 
     if (rt->gcIsNeeded) {
@@ -1953,22 +1953,20 @@ TriggerCompartmentGC(JSCompartment *comp
     TriggerAllOperationCallbacks(comp->rt);
 }
 
 void
 MaybeGC(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime;
 
-#ifdef JS_GC_ZEAL
-    if (rt->gcZeal > 0) {
+    if (rt->gcZeal()) {
         js_GC(cx, NULL, GC_NORMAL);
         return;
     }
-#endif
 
     JSCompartment *comp = cx->compartment;
     if (rt->gcIsNeeded) {
         js_GC(cx, (comp == rt->gcTriggerCompartment) ? comp : NULL, GC_NORMAL);
         return;
     }
 
     if (comp->gcBytes > 8192 && comp->gcBytes >= 3 * (comp->gcTriggerBytes / 4))
@@ -2266,21 +2264,17 @@ MarkAndSweep(JSContext *cx, JSCompartmen
     }
 #endif
 
     /*
      * Reset the property cache's type id generator so we can compress ids.
      * Same for the protoHazardShape proxy-shape standing in for all object
      * prototypes having readonly or setter properties.
      */
-    if (rt->shapeGen & SHAPE_OVERFLOW_BIT
-#ifdef JS_GC_ZEAL
-        || rt->gcZeal >= 1
-#endif
-        ) {
+    if (rt->shapeGen & SHAPE_OVERFLOW_BIT || (rt->gcZeal() && !rt->gcCurrentCompartment)) {
         rt->gcRegenShapes = true;
         rt->shapeGen = 0;
         rt->protoHazardShape = 0;
     }
 
     if (rt->gcCurrentCompartment) {
         rt->gcCurrentCompartment->purge(cx);
     } else {
@@ -2938,11 +2932,31 @@ NewCompartment(JSContext *cx, JSPrincipa
         if (rt->compartments.append(compartment))
             return compartment;
     }
     Foreground::delete_(compartment);
     JS_ReportOutOfMemory(cx);
     return NULL;
 }
 
+void
+RunDebugGC(JSContext *cx)
+{
+#ifdef JS_GC_ZEAL
+    if (IsGCAllowed(cx)) {
+        JSRuntime *rt = cx->runtime;
+
+        /*
+         * If rt->gcDebugCompartmentGC is true, only GC the current
+         * compartment. But don't GC the atoms compartment.
+         */
+        rt->gcTriggerCompartment = rt->gcDebugCompartmentGC ? cx->compartment : NULL;
+        if (rt->gcTriggerCompartment == rt->atomsCompartment)
+            rt->gcTriggerCompartment = NULL;
+
+        RunLastDitchGC(cx);
+    }
+#endif
+}
+
 } /* namespace gc */
 
 } /* namespace js */
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -864,27 +864,16 @@ class AutoIdVector;
 #ifdef DEBUG
 extern bool
 CheckAllocation(JSContext *cx);
 #endif
 
 extern JS_FRIEND_API(uint32)
 js_GetGCThingTraceKind(void *thing);
 
-#if 1
-/*
- * Since we're forcing a GC from JS_GC anyway, don't bother wasting cycles
- * loading oldval.  XXX remove implied force, fix jsinterp.c's "second arg
- * ignored", etc.
- */
-#define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JS_TRUE)
-#else
-#define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JSVAL_IS_GCTHING(oldval))
-#endif
-
 extern JSBool
 js_InitGC(JSRuntime *rt, uint32 maxbytes);
 
 extern void
 js_FinishGC(JSRuntime *rt);
 
 extern JSBool
 js_AddRoot(JSContext *cx, js::Value *vp, const char *name);
@@ -1312,16 +1301,20 @@ js_MarkTraps(JSTracer *trc);
 #endif
 
 namespace js {
 namespace gc {
 
 JSCompartment *
 NewCompartment(JSContext *cx, JSPrincipals *principals);
 
+/* Tries to run a GC no matter what (used for GC zeal). */
+void
+RunDebugGC(JSContext *cx);
+
 } /* namespace js */
 } /* namespace gc */
 
 inline JSCompartment *
 JSObject::getCompartment() const
 {
     return compartment();
 }
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -156,16 +156,37 @@ GetGCKindSlots(FinalizeKind thingKind)
       case FINALIZE_OBJECT16_BACKGROUND:
         return 16;
       default:
         JS_NOT_REACHED("Bad object finalize kind");
         return 0;
     }
 }
 
+static inline void
+GCPoke(JSContext *cx, Value oldval)
+{
+    /*
+     * Since we're forcing a GC from JS_GC anyway, don't bother wasting cycles
+     * loading oldval.  XXX remove implied force, fix jsinterp.c's "second arg
+     * ignored", etc.
+     */
+#if 1
+    cx->runtime->gcPoke = JS_TRUE;
+#else
+    cx->runtime->gcPoke = oldval.isGCThing();
+#endif
+
+#ifdef JS_GC_ZEAL
+    /* Schedule a GC to happen "soon" after a GC poke. */
+    if (cx->runtime->gcZeal())
+        cx->runtime->gcNextScheduled = 1;
+#endif
+}
+
 } /* namespace gc */
 } /* namespace js */
 
 /*
  * Allocates a new GC thing. After a successful allocation the caller must
  * fully initialize the thing before calling any function that can potentially
  * trigger GC. This will ensure that GC tracing never sees junk values stored
  * in the partially initialized thing.
@@ -178,16 +199,21 @@ NewFinalizableGCThing(JSContext *cx, uns
     JS_ASSERT(thingKind < js::gc::FINALIZE_LIMIT);
 #ifdef JS_THREADSAFE
     JS_ASSERT_IF((cx->compartment == cx->runtime->atomsCompartment),
                  (thingKind == js::gc::FINALIZE_STRING) ||
                  (thingKind == js::gc::FINALIZE_SHORT_STRING));
 #endif
     JS_ASSERT(!cx->runtime->gcRunning);
 
+#ifdef JS_GC_ZEAL
+    if (cx->runtime->needZealousGC())
+        js::gc::RunDebugGC(cx);
+#endif
+
     METER(cx->compartment->arenas[thingKind].stats.alloc++);
     js::gc::Cell *cell = cx->compartment->freeLists.getNext(thingKind);
     return static_cast<T *>(cell ? cell : js::gc::RefillFinalizableFreeList(cx, thingKind));
 }
 
 #undef METER
 #undef METER_IF
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5842,17 +5842,17 @@ js_DeleteProperty(JSContext *cx, JSObjec
         return true;
     }
 
     if (!CallJSPropertyOp(cx, obj->getClass()->delProperty, obj, SHAPE_USERID(shape), rval))
         return false;
 
     if (obj->containsSlot(shape->slot)) {
         const Value &v = obj->nativeGetSlot(shape->slot);
-        GC_POKE(cx, v);
+        GCPoke(cx, v);
 
         /*
          * Delete is rare enough that we can take the hit of checking for an
          * active cloned method function object that must be homed to a callee
          * slot on the active stack frame before this delete completes, in case
          * someone saved the clone and checks it against foo.caller for a foo
          * called from the active method.
          *
@@ -6522,17 +6522,17 @@ js_SetReservedSlot(JSContext *cx, JSObje
     if (slot >= obj->numSlots()) {
         uint32 nslots = JSSLOT_FREE(clasp);
         JS_ASSERT(slot < nslots);
         if (!obj->allocSlots(cx, nslots))
             return false;
     }
 
     obj->setSlot(slot, v);
-    GC_POKE(cx, JS_NULL);
+    GCPoke(cx, NullValue());
     return true;
 }
 
 GlobalObject *
 JSObject::getGlobal() const
 {
     JSObject *obj = const_cast<JSObject *>(this);
     while (JSObject *parent = obj->getParent())
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -630,17 +630,22 @@ usage(void)
     fprintf(gErrFile, "  -A <max>      After <max> memory allocations, act like we're OOM.\n");
     fprintf(gErrFile, "  -O            At exit, print the number of memory allocations in \n"
                       "                the program.\n");
 #endif
 #ifdef JS_THREADSAFE
     fprintf(gErrFile, "  -g <n>        Sleep for <n> seconds before starting (default: 0)\n");
 #endif
 #ifdef JS_GC_ZEAL
-    fprintf(gErrFile, "  -Z <n>        Toggle GC zeal: low if <n> is 0 (default), high if non-zero\n");
+    fprintf(gErrFile, "  -Z <n>[,<f>[,<c>]]  Set GC zeal to <n>.\n"
+                      "                Possible values for <n>:\n"
+                      "                  0:  no additional GCs\n"
+                      "                  1:  additional GCs at common danger points\n"
+                      "                  2:  GC every <f> allocations (<f> defaults to 100)\n"
+                      "                If <c> = 1, do compartment GCs. Otherwise full.\n");
 #endif
 #ifdef MOZ_TRACEVIS
     fprintf(gErrFile, "  -T  Start TraceVis\n");
 #endif
     return 2;
 }
 
 /*
@@ -697,16 +702,35 @@ extern JSClass global_class;
 
 #if defined(JS_TRACER) && defined(DEBUG)
 namespace js {
     extern struct JSClass jitstats_class;
     void InitJITStatsClass(JSContext *cx, JSObject *glob);
 }
 #endif
 
+#ifdef JS_GC_ZEAL
+static void
+ParseZealArg(JSContext *cx, const char *arg)
+{
+    int zeal, freq = 1, compartment = 0;
+    const char *p = strchr(arg, ',');
+
+    zeal = atoi(arg);
+    if (p) {
+        freq = atoi(p + 1);
+        p = strchr(p + 1, ',');
+        if (p)
+            compartment = atoi(p + 1);
+    }
+
+    JS_SetGCZeal(cx, zeal, freq, !!compartment);
+}
+#endif
+
 static int
 ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc)
 {
     int i, j, length;
     JSObject *argsObj;
     char *filename = NULL;
     JSBool isInteractive = JS_TRUE;
     JSBool forceTTY = JS_FALSE;
@@ -780,17 +804,17 @@ ProcessArgs(JSContext *cx, JSObject *obj
 
             JS_SetVersion(cx, (JSVersion) atoi(argv[i]));
             break;
 
 #ifdef JS_GC_ZEAL
         case 'Z':
             if (++i == argc)
                 return usage();
-            JS_SetGCZeal(cx, !!(atoi(argv[i])));
+            ParseZealArg(cx, argv[i]);
             break;
 #endif
 #ifdef DEBUG
         case 'A':
             /* Handled at the very start of main(). */
             ++i; /* skip the argument */
             break;
 
@@ -1463,18 +1487,25 @@ AssertJit(JSContext *cx, uintN argc, jsv
 
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return JS_TRUE;
 }
 
 static JSBool
 GC(JSContext *cx, uintN argc, jsval *vp)
 {
+    JSCompartment *comp = NULL;
+    if (argc == 1) {
+        Value arg = Valueify(vp[2]);
+        if (arg.isObject())
+            comp = arg.toObject().unwrap()->compartment();
+    }
+
     size_t preBytes = cx->runtime->gcBytes;
-    JS_GC(cx);
+    JS_CompartmentGC(cx, comp);
 
     char buf[256];
     JS_snprintf(buf, sizeof(buf), "before %lu, after %lu, break %08lx\n",
                 (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes,
 #ifdef HAVE_SBRK
                 (unsigned long)sbrk(0)
 #else
                 0
@@ -1560,21 +1591,56 @@ GCParameter(JSContext *cx, uintN argc, j
     *vp = JSVAL_VOID;
     return JS_TRUE;
 }
 
 #ifdef JS_GC_ZEAL
 static JSBool
 GCZeal(JSContext *cx, uintN argc, jsval *vp)
 {
-    uint32 zeal;
-
-    if (!JS_ValueToECMAUint32(cx, argc == 0 ? JSVAL_VOID : vp[2], &zeal))
+    uint32 zeal, frequency = JS_DEFAULT_ZEAL_FREQ;
+    JSBool compartment = JS_FALSE;
+
+    if (argc > 3) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TOO_MANY_ARGS, "gczeal");
+        return JS_FALSE;
+    }
+    if (!JS_ValueToECMAUint32(cx, argc < 1 ? JSVAL_VOID : vp[2], &zeal))
         return JS_FALSE;
-    JS_SetGCZeal(cx, (uint8)zeal);
+    if (argc >= 2)
+        if (!JS_ValueToECMAUint32(cx, vp[3], &frequency))
+            return JS_FALSE;
+    if (argc >= 3)
+        compartment = js_ValueToBoolean(Valueify(vp[3]));
+
+    JS_SetGCZeal(cx, (uint8)zeal, frequency, compartment);
+    *vp = JSVAL_VOID;
+    return JS_TRUE;
+}
+
+static JSBool
+ScheduleGC(JSContext *cx, uintN argc, jsval *vp)
+{
+    uint32 count;
+    bool compartment = false;
+
+    if (argc != 1 && argc != 2) {
+        JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL,
+                             (argc < 1)
+                             ? JSSMSG_NOT_ENOUGH_ARGS
+                             : JSSMSG_TOO_MANY_ARGS,
+                             "schedulegc");
+        return JS_FALSE;
+    }
+    if (!JS_ValueToECMAUint32(cx, vp[2], &count))
+        return JS_FALSE;
+    if (argc == 2)
+        compartment = js_ValueToBoolean(Valueify(vp[3]));
+
+    JS_ScheduleGC(cx, count, compartment);
     *vp = JSVAL_VOID;
     return JS_TRUE;
 }
 #endif /* JS_GC_ZEAL */
 
 typedef struct JSCountHeapNode JSCountHeapNode;
 
 struct JSCountHeapNode {
@@ -4729,17 +4795,18 @@ static JSFunctionSpec shell_functions[] 
 #ifdef JS_GCMETER
     JS_FN("gcstats",        GCStats,        0,0),
 #endif
     JS_FN("gcparam",        GCParameter,    2,0),
     JS_FN("countHeap",      CountHeap,      0,0),
     JS_FN("makeFinalizeObserver", MakeFinalizeObserver, 0,0),
     JS_FN("finalizeCount",  FinalizeCount, 0,0),
 #ifdef JS_GC_ZEAL
-    JS_FN("gczeal",         GCZeal,         1,0),
+    JS_FN("gczeal",         GCZeal,         2,0),
+    JS_FN("schedulegc",     ScheduleGC,     1,0),
 #endif
     JS_FN("setDebug",       SetDebug,       1,0),
     JS_FN("setDebuggerHandler", SetDebuggerHandler, 1,0),
     JS_FN("setThrowHook",   SetThrowHook,   1,0),
     JS_FN("trap",           Trap,           3,0),
     JS_FN("untrap",         Untrap,         2,0),
     JS_FN("line2pc",        LineToPC,       0,0),
     JS_FN("pc2line",        PCToLine,       0,0),
@@ -4832,17 +4899,18 @@ static const char *const shell_help_mess
 "putstr([exp])            Evaluate and print expression without newline",
 "dateNow()                    Return the current time with sub-ms precision",
 "help([name ...])         Display usage and help messages",
 "quit()                   Quit the shell",
 "assertEq(actual, expected[, msg])\n"
 "  Throw if the first two arguments are not the same (both +0 or both -0,\n"
 "  both NaN, or non-zero and ===)",
 "assertJit()              Throw if the calling function failed to JIT",
-"gc()                     Run the garbage collector",
+"gc([obj])                Run the garbage collector\n"
+"                         When obj is given, GC only the compartment it's in",
 #ifdef JS_GCMETER
 "gcstats()                Print garbage collector statistics",
 #endif
 "gcparam(name, value)\n"
 "  Wrapper for JS_SetGCParameter. The name must be either 'maxBytes' or\n"
 "  'maxMallocBytes' and the value must be convertable to a positive uint32",
 "countHeap([start[, kind]])\n"
 "  Count the number of live GC things in the heap or things reachable from\n"
@@ -4851,17 +4919,20 @@ static const char *const shell_help_mess
 "  'qname', 'namespace', 'xml' to count only things of that kind",
 "makeFinalizeObserver()\n"
 "  get a special object whose finalization increases the counter returned\n"
 "  by the finalizeCount function",
 "finalizeCount()\n"
 "  return the current value of the finalization counter that is incremented\n"
 "  each time an object returned by the makeFinalizeObserver is finalized",
 #ifdef JS_GC_ZEAL
-"gczeal(level)            How zealous the garbage collector should be",
+"gczeal(level, [freq], [compartmentGC?])\n"
+"                         How zealous the garbage collector should be",
+"schedulegc(num, [compartmentGC?])\n"
+"                         Schedule a GC to happen after num allocations",
 #endif
 "setDebug(debug)          Set debug mode",
 "setDebuggerHandler(f)    Set handler for debugger keyword to f",
 "setThrowHook(f)          Set throw hook to f",
 "trap([fun, [pc,]] exp)   Trap bytecode execution",
 "untrap(fun[, pc])        Remove a trap",
 "line2pc([fun,] line)     Map line number to PC",
 "pc2line(fun[, pc])       Map PC to line number",
@@ -6041,16 +6112,17 @@ main(int argc, char **argv, char **envp)
     if (!InitWatchdog(rt))
         return 1;
 
     cx = NewContext(rt);
     if (!cx)
         return 1;
 
     JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_ANONFUNFIX);
+    JS_SetGCParameter(rt, JSGC_MODE, JSGC_MODE_COMPARTMENT);
     JS_SetGCParameterForThread(cx, JSGC_MAX_CODE_CACHE_BYTES, 16 * 1024 * 1024);
 
     result = Shell(cx, argc, argv, envp);
 
 #ifdef DEBUG
     if (OOM_printAllocationCount)
         printf("OOM max count: %u\n", OOM_counter);
 #endif
--- a/js/src/xpconnect/shell/xpcshell.cpp
+++ b/js/src/xpconnect/shell/xpcshell.cpp
@@ -566,17 +566,17 @@ GC(JSContext *cx, uintN argc, jsval *vp)
 #ifdef JS_GC_ZEAL
 static JSBool
 GCZeal(JSContext *cx, uintN argc, jsval *vp)
 {
     uint32 zeal;
     if (!JS_ValueToECMAUint32(cx, argc ? JS_ARGV(cx, vp)[0] : JSVAL_VOID, &zeal))
         return JS_FALSE;
 
-    JS_SetGCZeal(cx, (PRUint8)zeal);
+    JS_SetGCZeal(cx, (PRUint8)zeal, JS_DEFAULT_ZEAL_FREQ, JS_FALSE);
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return JS_TRUE;
 }
 #endif
 
 #ifdef DEBUG
 
 static JSBool