diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index c47341508124..d938280934cc 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -570,6 +570,8 @@ GCState(JSContext *cx, unsigned argc, jsval *vp) state = "mark"; else if (globalState == gc::SWEEP) state = "sweep"; + else if (globalState == gc::COMPACT) + state = "compact"; else MOZ_CRASH("Unobserveable global GC state"); @@ -597,6 +599,48 @@ DeterministicGC(JSContext *cx, unsigned argc, jsval *vp) } #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) { @@ -616,7 +660,12 @@ GCSlice(JSContext *cx, unsigned argc, Value *vp) 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; } @@ -2190,7 +2239,7 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = { " 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, @@ -2305,9 +2354,15 @@ gc::ZealModeHelpText), " 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)", diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index a79545fd8440..db6dced40200 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -320,7 +320,8 @@ class GCRuntime 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(); diff --git a/js/src/jit-test/tests/gc/incremental-compacting.js b/js/src/jit-test/tests/gc/incremental-compacting.js new file mode 100644 index 000000000000..1239065ec735 --- /dev/null +++ b/js/src/jit-test/tests/gc/incremental-compacting.js @@ -0,0 +1,40 @@ +// Exercise incremental compacting GC +// Run with MOZ_GCTIMER to see the timings + +if (!("gcstate" in this && "gczeal" in this)) + quit(); + +gczeal(0); + +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); diff --git a/js/src/jsapi-tests/testGCFinalizeCallback.cpp b/js/src/jsapi-tests/testGCFinalizeCallback.cpp index b7c681b9aabb..10f7df6a06ad 100644 --- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp +++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp @@ -88,13 +88,13 @@ BEGIN_TEST(testGCFinalizeCallback) 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()); diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index 632e2ce707ea..9569dd677b74 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -91,7 +91,8 @@ BEGIN_TEST(testWeakMap_keyDelegates) */ 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 @@ -105,7 +106,8 @@ BEGIN_TEST(testWeakMap_keyDelegates) 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)); /* diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 107b6d79279e..aaef1c085b0e 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -6402,16 +6402,21 @@ ZonesSelected(JSRuntime *rt) } void -GCRuntime::gcDebugSlice(SliceBudget &budget) +GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget &budget) { - if (!ZonesSelected(rt)) { - if (isIncrementalGCInProgress()) - JS::PrepareForIncrementalGC(rt); - else - JS::PrepareForFullGC(rt); - } - if (!isIncrementalGCInProgress()) - invocationKind = GC_NORMAL; + 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); }