Bug 742570 - Improve shell control of incremental GC (r=igor)

This commit is contained in:
Bill McCloskey 2012-04-04 15:07:36 -07:00
parent 6090417a56
commit 6deca8df52
8 changed files with 129 additions and 26 deletions

View File

@ -210,6 +210,23 @@ ScheduleGC(JSContext *cx, unsigned argc, jsval *vp)
return JS_TRUE;
}
static JSBool
SelectForGC(JSContext *cx, unsigned argc, jsval *vp)
{
JSRuntime *rt = cx->runtime;
for (unsigned i = 0; i < argc; i++) {
Value arg(JS_ARGV(cx, vp)[i]);
if (arg.isObject()) {
if (!rt->gcSelectedForMarking.append(&arg.toObject()))
return false;
}
}
*vp = JSVAL_VOID;
return true;
}
static JSBool
VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp)
{
@ -225,17 +242,22 @@ VerifyBarriers(JSContext *cx, unsigned argc, jsval *vp)
static JSBool
GCSlice(JSContext *cx, unsigned argc, jsval *vp)
{
uint32_t budget;
bool limit = true;
uint32_t budget = 0;
if (argc != 1) {
if (argc > 1) {
ReportUsageError(cx, &JS_CALLEE(cx, vp).toObject(), "Wrong number of arguments");
return JS_FALSE;
}
if (!JS_ValueToECMAUint32(cx, vp[2], &budget))
return JS_FALSE;
if (argc == 1) {
if (!JS_ValueToECMAUint32(cx, vp[2], &budget))
return false;
} else {
limit = false;
}
GCDebugSlice(cx, budget);
GCDebugSlice(cx, limit, budget);
*vp = JSVAL_VOID;
return JS_TRUE;
}
@ -536,6 +558,10 @@ static JSFunctionSpecWithHelp TestingFunctions[] = {
" 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("selectforgc", SelectForGC, 0, 0,
"selectforgc(obj1, obj2, ...)",
" Schedule the given objects to be marked in the next GC slice."),
JS_FN_HELP("verifybarriers", VerifyBarriers, 0, 0,
"verifybarriers()",
" Start or end a run of the write barrier verifier."),

View File

@ -11,3 +11,11 @@ if (!("gczeal" in this)) {
if (!("schedulegc" in this)) {
schedulegc = function() { }
}
if (!("gcslice" in this)) {
gcslice = function() { }
}
if (!("selectforgc" in this)) {
selectforgc = function() { }
}

View File

@ -0,0 +1,31 @@
var objs;
function init()
{
objs = new Object();
var x = new Object();
objs.root1 = x;
objs.root2 = new Object();
x.ptr = new Object();
x = null;
/*
* Clears out the arena lists. Otherwise all the objects above
* would be considered to be created during the incremental GC.
*/
gc();
}
/*
* Use eval here so that the interpreter frames end up higher on the
* stack, which avoids them being seen later on by the conservative
* scanner.
*/
eval("init()");
gcslice(0);
selectforgc(objs.root2);
gcslice(1);
objs.root2.ptr = objs.root1.ptr;
objs.root1.ptr = null;
gcslice();

View File

@ -0,0 +1,10 @@
/* Exercise the path where we want to collect a new compartment in the middle of incremental GC. */
var g1 = newGlobal('new-compartment');
var g2 = newGlobal('new-compartment');
schedulegc(g1);
gcslice(0);
schedulegc(g2);
gcslice(1);
gcslice();

View File

@ -0,0 +1,11 @@
/* Exercise the path where we want to collect a new compartment in the middle of incremental GC. */
var g1 = newGlobal('new-compartment');
var g2 = newGlobal('new-compartment');
schedulegc(g1);
schedulegc(g2);
gcslice(0);
schedulegc(g1);
gcslice(1);
gcslice();

View File

@ -447,6 +447,8 @@ struct JSRuntime : js::RuntimeFriendFields
int gcNextScheduled;
bool gcDeterministicOnly;
js::Vector<JSObject *, 0, js::SystemAllocPolicy> gcSelectedForMarking;
int gcZeal() { return gcZeal_; }
bool needZealousGC() {

View File

@ -1768,7 +1768,8 @@ SliceBudget::TimeBudget(int64_t millis)
/* static */ int64_t
SliceBudget::WorkBudget(int64_t work)
{
return -work;
/* For work = 0 not to mean Unlimited, we subtract 1. */
return -work - 1;
}
SliceBudget::SliceBudget()
@ -1787,7 +1788,7 @@ SliceBudget::SliceBudget(int64_t budget)
counter = CounterReset;
} else {
deadline = 0;
counter = -budget;
counter = -budget - 1;
}
}
@ -1956,7 +1957,8 @@ GCMarker::markDelayedChildren(SliceBudget &budget)
markLaterArenas--;
markDelayedChildren(aheader);
if (budget.checkOverBudget())
budget.step(150);
if (budget.isOverBudget())
return false;
} while (unmarkedArenaStackTop);
JS_ASSERT(!markLaterArenas);
@ -3073,10 +3075,6 @@ ValidateIncrementalMarking(JSContext *cx)
uintptr_t end = arena->thingsEnd();
while (thing < end) {
Cell *cell = (Cell *)thing;
if (bitmap->isMarked(cell, BLACK) && !incBitmap.isMarked(cell, BLACK)) {
JS_DumpHeap(rt, stdout, NULL, JSGCTraceKind(0), NULL, 100000, NULL);
printf("Assertion cell: %p (%d)\n", (void *)cell, cell->getAllocKind());
}
JS_ASSERT_IF(bitmap->isMarked(cell, BLACK), incBitmap.isMarked(cell, BLACK));
thing += Arena::thingSize(kind);
}
@ -3317,6 +3315,11 @@ AutoGCSession::~AutoGCSession()
runtime->gcNextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
runtime->gcChunkAllocationSinceLastGC = false;
#ifdef JS_GC_ZEAL
/* Keeping these around after a GC is dangerous. */
runtime->gcSelectedForMarking.clearAndFree();
#endif
}
static void
@ -3436,11 +3439,21 @@ IncrementalGCSlice(JSContext *cx, int64_t budget, JSGCInvocationKind gckind)
if (!rt->gcMarker.hasBufferedGrayRoots())
sliceBudget.reset();
#ifdef JS_GC_ZEAL
if (!rt->gcSelectedForMarking.empty()) {
for (JSObject **obj = rt->gcSelectedForMarking.begin();
obj != rt->gcSelectedForMarking.end(); obj++)
{
MarkObjectUnbarriered(&rt->gcMarker, obj, "selected obj");
}
}
#endif
bool finished = rt->gcMarker.drainMarkStack(sliceBudget);
if (finished) {
JS_ASSERT(rt->gcMarker.isDrained());
if (initialState == MARK && !rt->gcLastMarkSlice)
if (initialState == MARK && !rt->gcLastMarkSlice && budget != SliceBudget::Unlimited)
rt->gcLastMarkSlice = true;
else
rt->gcIncrementalState = SWEEP;
@ -3545,7 +3558,7 @@ BudgetIncrementalGC(JSRuntime *rt, int64_t *budget)
* the marking implementation.
*/
static JS_NEVER_INLINE void
GCCycle(JSContext *cx, int64_t budget, JSGCInvocationKind gckind)
GCCycle(JSContext *cx, bool incremental, int64_t budget, JSGCInvocationKind gckind)
{
JSRuntime *rt = cx->runtime;
@ -3579,7 +3592,7 @@ GCCycle(JSContext *cx, int64_t budget, JSGCInvocationKind gckind)
}
#endif
if (budget == SliceBudget::Unlimited) {
if (!incremental) {
/* If non-incremental GC was requested, reset incremental GC. */
ResetIncrementalGC(rt, "requested");
rt->gcStats.nonincremental("requested");
@ -3626,7 +3639,8 @@ IsDeterministicGCReason(gcreason::Reason reason)
#endif
static void
Collect(JSContext *cx, int64_t budget, JSGCInvocationKind gckind, gcreason::Reason reason)
Collect(JSContext *cx, bool incremental, int64_t budget,
JSGCInvocationKind gckind, gcreason::Reason reason)
{
JSRuntime *rt = cx->runtime;
JS_AbortIfWrongThread(rt);
@ -3636,7 +3650,7 @@ Collect(JSContext *cx, int64_t budget, JSGCInvocationKind gckind, gcreason::Reas
return;
#endif
JS_ASSERT_IF(budget != SliceBudget::Unlimited, JSGC_INCREMENTAL);
JS_ASSERT_IF(!incremental || budget != SliceBudget::Unlimited, JSGC_INCREMENTAL);
#ifdef JS_GC_ZEAL
bool restartVerify = cx->runtime->gcVerifyData &&
@ -3693,7 +3707,7 @@ Collect(JSContext *cx, int64_t budget, JSGCInvocationKind gckind, gcreason::Reas
/* Lock out other GC allocator and collector invocations. */
AutoLockGC lock(rt);
rt->gcPoke = false;
GCCycle(cx, budget, gckind);
GCCycle(cx, incremental, budget, gckind);
}
if (rt->gcIncrementalState == NO_INCREMENTAL) {
@ -3714,20 +3728,21 @@ namespace js {
void
GC(JSContext *cx, JSGCInvocationKind gckind, gcreason::Reason reason)
{
Collect(cx, SliceBudget::Unlimited, gckind, reason);
Collect(cx, false, SliceBudget::Unlimited, gckind, reason);
}
void
GCSlice(JSContext *cx, JSGCInvocationKind gckind, gcreason::Reason reason)
{
Collect(cx, cx->runtime->gcSliceBudget, gckind, reason);
Collect(cx, true, cx->runtime->gcSliceBudget, gckind, reason);
}
void
GCDebugSlice(JSContext *cx, int64_t objCount)
GCDebugSlice(JSContext *cx, bool limit, int64_t objCount)
{
int64_t budget = limit ? SliceBudget::WorkBudget(objCount) : SliceBudget::Unlimited;
PrepareForDebugGC(cx->runtime);
Collect(cx, SliceBudget::WorkBudget(objCount), GC_NORMAL, gcreason::API);
Collect(cx, true, budget, GC_NORMAL, gcreason::API);
}
/* Schedule a full GC unless a compartment will already be collected. */

View File

@ -1404,7 +1404,7 @@ extern void
GCSlice(JSContext *cx, JSGCInvocationKind gckind, js::gcreason::Reason reason);
extern void
GCDebugSlice(JSContext *cx, int64_t objCount);
GCDebugSlice(JSContext *cx, bool limit, int64_t objCount);
extern void
PrepareForDebugGC(JSRuntime *rt);
@ -1731,14 +1731,14 @@ struct SliceBudget {
counter = INTPTR_MAX;
}
void step() {
counter--;
void step(intptr_t amt = 1) {
counter -= amt;
}
bool checkOverBudget();
bool isOverBudget() {
if (counter > 0)
if (counter >= 0)
return false;
return checkOverBudget();
}