Bug 1118996 - Add gcstart() test function to start an incremental GC r=terrence
☠☠ backed out by 1f8566481a95 ☠ ☠
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 12 Jan 2015 10:29:38 +0000
changeset 223338 55b18ee8ffb736c3ea5a0221da4f5e656b572eec
parent 223337 74895ad1425a90477eea77111a2c8f966e9e882f
child 223339 1f8566481a95af76e7dabbd5d6f9bab6b7d89d4e
push idunknown
push userunknown
push dateunknown
reviewersterrence
bugs1118996
milestone37.0a1
Bug 1118996 - Add gcstart() test function to start an incremental GC r=terrence :
js/src/builtin/TestingFunctions.cpp
js/src/gc/GCRuntime.h
js/src/jit-test/tests/gc/incremental-compacting.js
js/src/jsapi-tests/testGCFinalizeCallback.cpp
js/src/jsapi-tests/testWeakMap.cpp
js/src/jsgc.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -565,16 +565,18 @@ GCState(JSContext *cx, unsigned argc, js
     const char *state;
     gc::State globalState = cx->runtime()->gc.state();
     if (globalState == gc::NO_INCREMENTAL)
         state = "none";
     else if (globalState == gc::MARK)
         state = "mark";
     else if (globalState == gc::SWEEP)
         state = "sweep";
+    else if (globalState == gc::COMPACT)
+        state = "compact";
     else
         MOZ_CRASH("Unobserveable global GC state");
 
     JSString *str = JS_NewStringCopyZ(cx, state);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
@@ -593,16 +595,58 @@ DeterministicGC(JSContext *cx, unsigned 
 
     cx->runtime()->gc.setDeterministic(ToBoolean(args[0]));
     args.rval().setUndefined();
     return true;
 }
 #endif /* JS_GC_ZEAL */
 
 static bool
+StartGC(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() > 2) {
+        RootedObject callee(cx, &args.callee());
+        ReportUsageError(cx, callee, "Wrong number of arguments");
+        return false;
+    }
+
+    SliceBudget budget;
+    if (args.length() >= 1) {
+        uint32_t work = 0;
+        if (!ToUint32(cx, args[0], &work))
+            return false;
+        budget = SliceBudget(WorkBudget(work));
+    }
+
+    bool shrinking = false;
+    if (args.length() >= 2) {
+        Value arg = args[1];
+        if (arg.isString()) {
+            if (!JS_StringEqualsAscii(cx, arg.toString(), "shrinking", &shrinking))
+                return false;
+        }
+    }
+
+    JSRuntime *rt = cx->runtime();
+    if (rt->gc.isIncrementalGCInProgress()) {
+        RootedObject callee(cx, &args.callee());
+        ReportUsageError(cx, callee, "Incremental GC already in progress");
+        return false;
+    }
+
+    JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
+    rt->gc.startDebugGC(gckind, budget);
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 GCSlice(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() > 1) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return false;
@@ -611,17 +655,22 @@ GCSlice(JSContext *cx, unsigned argc, Va
     SliceBudget budget;
     if (args.length() == 1) {
         uint32_t work = 0;
         if (!ToUint32(cx, args[0], &work))
             return false;
         budget = SliceBudget(WorkBudget(work));
     }
 
-    cx->runtime()->gc.gcDebugSlice(budget);
+    JSRuntime *rt = cx->runtime();
+    if (!rt->gc.isIncrementalGCInProgress())
+        rt->gc.startDebugGC(GC_NORMAL, budget);
+    else
+        rt->gc.debugGCSlice(budget);
+
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 ValidateGC(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -2217,17 +2266,17 @@ SetImmutablePrototype(JSContext *cx, uns
 }
 
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment' [, 'shrinking'])",
 "  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.\n"
-"  If 'shrinking' is passes as the optional second argument, perform a\n"
+"  If 'shrinking' is passed as the optional second argument, perform a\n"
 "  shrinking GC rather than a normal GC."),
 
     JS_FN_HELP("minorgc", ::MinorGC, 0, 0,
 "minorgc([aboutToOverflow])",
 "  Run a minor collector on the Nursery. When aboutToOverflow is true, marks\n"
 "  the store buffer as about-to-overflow before collecting."),
 
     JS_FN_HELP("gcparam", GCParameter, 2, 0,
@@ -2334,19 +2383,25 @@ gc::ZealModeHelpText),
 "gcstate()",
 "  Report the global GC state."),
 
     JS_FN_HELP("deterministicgc", DeterministicGC, 1, 0,
 "deterministicgc(true|false)",
 "  If true, only allow determinstic GCs to run."),
 #endif
 
+    JS_FN_HELP("startgc", StartGC, 1, 0,
+"startgc([n [, 'shrinking']])",
+"  Start an incremental GC and run a slice that processes about n objects.\n"
+"  If 'shrinking' is passesd as the optional second argument, perform a\n"
+"  shrinking GC rather than a normal GC."),
+
     JS_FN_HELP("gcslice", GCSlice, 1, 0,
-"gcslice(n)",
-"  Run an incremental GC slice that marks about n objects."),
+"gcslice([n])",
+"  Start or continue an an incremental GC, running a slice that processes about n objects."),
 
     JS_FN_HELP("validategc", ValidateGC, 1, 0,
 "validategc(true|false)",
 "  If true, a separate validation step is performed after an incremental GC."),
 
     JS_FN_HELP("fullcompartmentchecks", FullCompartmentChecks, 1, 0,
 "fullcompartmentchecks(true|false)",
 "  If true, check for compartment mismatches before every GC."),
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -315,17 +315,18 @@ class GCRuntime
         gcstats::AutoPhase ap(stats, gcstats::PHASE_EVICT_NURSERY);
         minorGCImpl(reason, nullptr);
     }
     bool gcIfNeeded(JSContext *cx = nullptr);
     void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
     void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
     void gcSlice(JS::gcreason::Reason reason, int64_t millis = 0);
     void finishGC(JS::gcreason::Reason reason);
-    void gcDebugSlice(SliceBudget &budget);
+    void startDebugGC(JSGCInvocationKind gckind, SliceBudget &budget);
+    void debugGCSlice(SliceBudget &budget);
 
     void runDebugGC();
     inline void poke();
 
     enum TraceOrMarkRuntime {
         TraceRuntime,
         MarkRuntime
     };
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/incremental-compacting.js
@@ -0,0 +1,35 @@
+// Exercise incremental compacting GC
+// Run with MOZ_GCTIMER to see the timings
+
+function testCompacting(zoneCount, objectCount, sliceCount)
+{
+    // Allocate objectCount objects in zoneCount zones
+    // On linux64 debug builds we will move them all
+    // Run compacting GC with multiple slices
+
+    var zones = [];
+    for (var i = 0; i < zoneCount; i++) {
+        var zone = newGlobal();
+        evaluate("var objects; " +
+                 "function makeObjectGraph(objectCount) { " +
+                 "    objects = []; " +
+                 "    for (var i = 0; i < objectCount; i++) " +
+                 "        objects.push({ serial: i }); " +
+                "}",
+                 { global: zone });
+        zone.makeObjectGraph(objectCount);
+        zones.push(zone);
+    }
+
+    startgc(sliceCount, "shrinking");
+    while (gcstate() !== "none") {
+        gcslice(sliceCount);
+    }
+
+    return zones;
+}
+
+testCompacting(1, 100000, 100000);
+testCompacting(2, 100000, 100000);
+testCompacting(4, 50000,  100000);
+testCompacting(2, 100000, 50000);
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -83,23 +83,23 @@ BEGIN_TEST(testGCFinalizeCallback)
 #ifdef JS_GC_ZEAL
 
     /* Full GC with reset due to new compartment, becoming compartment GC. */
 
     FinalizeCalls = 0;
     JS_SetGCZeal(cx, 9, 1000000);
     JS::PrepareForFullGC(rt);
     js::SliceBudget budget(js::WorkBudget(1));
-    rt->gc.gcDebugSlice(budget);
+    rt->gc.startDebugGC(GC_NORMAL, budget);
     CHECK(rt->gc.state() == js::gc::MARK);
     CHECK(rt->gc.isFullGc());
 
     JS::RootedObject global4(cx, createTestGlobal());
     budget = js::SliceBudget(js::WorkBudget(1));
-    rt->gc.gcDebugSlice(budget);
+    rt->gc.debugGCSlice(budget);
     CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
 
     for (unsigned i = 0; i < FinalizeCalls - 1; ++i)
         CHECK(!IsCompartmentGCBuffer[i]);
     CHECK(IsCompartmentGCBuffer[FinalizeCalls - 1]);
--- a/js/src/jsapi-tests/testWeakMap.cpp
+++ b/js/src/jsapi-tests/testWeakMap.cpp
@@ -86,31 +86,33 @@ BEGIN_TEST(testWeakMap_keyDelegates)
     keyDelegate = delegate;
 
     /*
      * Perform an incremental GC, introducing an unmarked CCW to force the map
      * zone to finish marking before the delegate zone.
      */
     CHECK(newCCW(map, delegate));
     js::SliceBudget budget(js::WorkBudget(1000000));
-    rt->gc.gcDebugSlice(budget);
+    rt->gc.startDebugGC(GC_NORMAL, budget);
+    CHECK(!JS::IsIncrementalGCInProgress(rt));
 #ifdef DEBUG
     CHECK(map->zone()->lastZoneGroupIndex() < delegate->zone()->lastZoneGroupIndex());
 #endif
 
     /* Add our entry to the weakmap. */
     JS::RootedValue val(cx, JS::Int32Value(1));
     CHECK(SetWeakMapEntry(cx, map, key, val));
     CHECK(checkSize(map, 1));
 
     /* Check the delegate keeps the entry alive even if the key is not reachable. */
     key = nullptr;
     CHECK(newCCW(map, delegate));
     budget = js::SliceBudget(js::WorkBudget(100000));
-    rt->gc.gcDebugSlice(budget);
+    rt->gc.startDebugGC(GC_NORMAL, budget);
+    CHECK(!JS::IsIncrementalGCInProgress(rt));
     CHECK(checkSize(map, 1));
 
     /*
      * Check that the zones finished marking at the same time, which is
      * neccessary because of the presence of the delegate and the CCW.
      */
 #ifdef DEBUG
     CHECK(map->zone()->lastZoneGroupIndex() == delegate->zone()->lastZoneGroupIndex());
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6397,26 +6397,31 @@ ZonesSelected(JSRuntime *rt)
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (zone->isGCScheduled())
             return true;
     }
     return false;
 }
 
 void
-GCRuntime::gcDebugSlice(SliceBudget &budget)
-{
-    if (!ZonesSelected(rt)) {
-        if (isIncrementalGCInProgress())
-            JS::PrepareForIncrementalGC(rt);
-        else
-            JS::PrepareForFullGC(rt);
-    }
-    if (!isIncrementalGCInProgress())
-        invocationKind = GC_NORMAL;
+GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget &budget)
+{
+    MOZ_ASSERT(!isIncrementalGCInProgress());
+    if (!ZonesSelected(rt))
+        JS::PrepareForFullGC(rt);
+    invocationKind = gckind;
+    collect(true, budget, JS::gcreason::DEBUG_GC);
+}
+
+void
+GCRuntime::debugGCSlice(SliceBudget &budget)
+{
+    MOZ_ASSERT(isIncrementalGCInProgress());
+    if (!ZonesSelected(rt))
+        JS::PrepareForIncrementalGC(rt);
     collect(true, budget, JS::gcreason::DEBUG_GC);
 }
 
 /* Schedule a full GC unless a zone will already be collected. */
 void
 js::PrepareForDebugGC(JSRuntime *rt)
 {
     if (!ZonesSelected(rt))