Bug 742570 - Improve GC testing functions (r=igor)
authorBill McCloskey <wmccloskey@mozilla.com>
Tue, 03 Apr 2012 14:07:38 -0700
changeset 91126 15a23c3923ff9752395c339165cdf113629608a4
parent 91125 bc7f3c6766263a33daf4fe0817c9d7b5fb738014
child 91127 a0a7af840b83b2e5567ced718fdd7d9af968c0e3
push id8033
push userwmccloskey@mozilla.com
push dateThu, 05 Apr 2012 22:47:07 +0000
treeherdermozilla-inbound@a0a7af840b83 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs742570
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 742570 - Improve GC testing functions (r=igor)
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/gc/multi-01.js
js/src/jsgc.cpp
js/src/jsgc.h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -16,29 +16,40 @@
 #include "methodjit/MethodJIT.h"
 
 using namespace js;
 using namespace JS;
 
 static JSBool
 GC(JSContext *cx, unsigned argc, jsval *vp)
 {
-    JSCompartment *comp = NULL;
+    /*
+     * If the first argument is 'compartment', we collect any compartments
+     * previously scheduled for GC via schedulegc. If the first argument is an
+     * object, we collect the object's compartment (any any other compartments
+     * scheduled for GC). Otherwise, we collect call compartments.
+     */
+    JSBool compartment = false;
     if (argc == 1) {
         Value arg = vp[2];
-        if (arg.isObject())
-            comp = UnwrapObject(&arg.toObject())->compartment();
+        if (arg.isString()) {
+            if (!JS_StringEqualsAscii(cx, arg.toString(), "compartment", &compartment))
+                return false;
+        } else if (arg.isObject()) {
+            PrepareCompartmentForGC(UnwrapObject(&arg.toObject())->compartment());
+            compartment = true;
+        }
     }
 
 #ifndef JS_MORE_DETERMINISTIC
     size_t preBytes = cx->runtime->gcBytes;
 #endif
 
-    if (comp)
-        PrepareCompartmentForGC(comp);
+    if (compartment)
+        PrepareForDebugGC(cx->runtime);
     else
         PrepareForFullGC(cx->runtime);
     GCForReason(cx, gcreason::API);
 
     char buf[256] = { '\0' };
 #ifndef JS_MORE_DETERMINISTIC
     JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n",
                 (unsigned long)preBytes, (unsigned long)cx->runtime->gcBytes);
@@ -172,26 +183,34 @@ GCZeal(JSContext *cx, unsigned argc, jsv
     JS_SetGCZeal(cx, (uint8_t)zeal, frequency);
     *vp = JSVAL_VOID;
     return JS_TRUE;
 }
 
 static JSBool
 ScheduleGC(JSContext *cx, unsigned argc, jsval *vp)
 {
-    uint32_t count;
-
     if (argc != 1) {
         ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
         return JS_FALSE;
     }
-    if (!JS_ValueToECMAUint32(cx, vp[2], &count))
-        return JS_FALSE;
 
-    JS_ScheduleGC(cx, count);
+    Value arg(vp[2]);
+    if (arg.isInt32()) {
+        /* Schedule a GC to happen after |arg| allocations. */
+        JS_ScheduleGC(cx, arg.toInt32());
+    } else if (arg.isObject()) {
+        /* Ensure that |comp| is collected during the next GC. */
+        JSCompartment *comp = UnwrapObject(&arg.toObject())->compartment();
+        PrepareCompartmentForGC(comp);
+    } else if (arg.isString()) {
+        /* This allows us to schedule atomsCompartment for GC. */
+        PrepareCompartmentForGC(arg.toString()->compartment());
+    }
+
     *vp = JSVAL_VOID;
     return JS_TRUE;
 }
 
 static JSBool
 VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp)
 {
     if (argc) {
@@ -468,18 +487,20 @@ static JSBool
 Terminate(JSContext *cx, unsigned arg, jsval *vp)
 {
     JS_ClearPendingException(cx);
     return JS_FALSE;
 }
 
 static JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
-"gc([obj])",
-"  Run the garbage collector. When obj is given, GC only its compartment."),
+"gc([obj] | 'compartment')",
+"  Run the garbage collector. When obj is given, GC only its compartment.\n"
+"  If 'compartment' is given, GC any compartments that were scheduled for\n"
+"  GC via schedulegc."),
 
     JS_FN_HELP("gcparam", GCParameter, 2, 0,
 "gcparam(name [, value])",
 "  Wrapper for JS_[GS]etGCParameter. The name is either maxBytes,\n"
 "  maxMallocBytes, gcBytes, gcNumber, or sliceTimeBudget."),
 
     JS_FN_HELP("countHeap", CountHeap, 0, 0,
 "countHeap([start[, kind]])",
@@ -506,18 +527,19 @@ static JSFunctionSpecWithHelp TestingFun
 "    1: Collect when roots are added or removed\n"
 "    2: Collect when memory is allocated\n"
 "    3: Collect when the window paints (browser only)\n"
 "    4: Verify write barriers between instructions\n"
 "    5: Verify write barriers between paints\n"
 "  Period specifies that collection happens every n allocations.\n"),
 
     JS_FN_HELP("schedulegc", ScheduleGC, 1, 0,
-"schedulegc(num)",
-"  Schedule a GC to happen after num allocations."),
+"schedulegc(num | obj)",
+"  If num is given, schedule a GC after num allocations.\n"
+"  If obj is given, schedule a GC of obj's compartment."),
 
     JS_FN_HELP("verifybarriers", VerifyBarriers, 0, 0,
 "verifybarriers()",
 "  Start or end a run of the write barrier verifier."),
 
     JS_FN_HELP("gcslice", GCSlice, 1, 0,
 "gcslice(n)",
 "  Run an incremental GC slice that marks about n objects."),
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/multi-01.js
@@ -0,0 +1,9 @@
+/* Make sure we don't collect the atoms compartment unless every compartment is marked. */
+
+var g = newGlobal('new-compartment');
+g.eval("var x = 'some-atom';");
+
+schedulegc(this);
+schedulegc('atoms');
+gc('compartment');
+print(g.x);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3721,20 +3721,32 @@ void
 GCSlice(JSContext *cx, JSGCInvocationKind gckind, gcreason::Reason reason)
 {
     Collect(cx, cx->runtime->gcSliceBudget, gckind, reason);
 }
 
 void
 GCDebugSlice(JSContext *cx, int64_t objCount)
 {
-    PrepareForFullGC(cx->runtime);
+    PrepareForDebugGC(cx->runtime);
     Collect(cx, SliceBudget::WorkBudget(objCount), GC_NORMAL, gcreason::API);
 }
 
+/* Schedule a full GC unless a compartment will already be collected. */
+void
+PrepareForDebugGC(JSRuntime *rt)
+{
+    for (CompartmentsIter c(rt); !c.done(); c.next()) {
+        if (c->isGCScheduled())
+            return;
+    }
+
+    PrepareForFullGC(rt);
+}
+
 void
 ShrinkGCBuffers(JSRuntime *rt)
 {
     AutoLockGC lock(rt);
     JS_ASSERT(!rt->gcRunning);
 #ifndef JS_THREADSAFE
     ExpireChunksAndArenas(rt, true);
 #else
@@ -3914,17 +3926,17 @@ NewCompartment(JSContext *cx, JSPrincipa
     Foreground::delete_(compartment);
     return NULL;
 }
 
 void
 RunDebugGC(JSContext *cx)
 {
 #ifdef JS_GC_ZEAL
-    PrepareForFullGC(cx->runtime);
+    PrepareForDebugGC(cx->runtime);
     RunLastDitchGC(cx, gcreason::DEBUG_GC);
 #endif
 }
 
 void
 SetDeterministicGC(JSContext *cx, bool enabled)
 {
 #ifdef JS_GC_ZEAL
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1401,16 +1401,19 @@ extern void
 GC(JSContext *cx, JSGCInvocationKind gckind, js::gcreason::Reason reason);
 
 extern void
 GCSlice(JSContext *cx, JSGCInvocationKind gckind, js::gcreason::Reason reason);
 
 extern void
 GCDebugSlice(JSContext *cx, int64_t objCount);
 
+extern void
+PrepareForDebugGC(JSRuntime *rt);
+
 } /* namespace js */
 
 namespace js {
 
 void
 InitTracer(JSTracer *trc, JSRuntime *rt, JSTraceCallback callback);
 
 #ifdef JS_THREADSAFE