Bug 650978 - Add gczeal setting to GC on every allocation (r=gwagner)

This commit is contained in:
Bill McCloskey 2011-06-01 17:48:52 -07:00
parent 9a7e3e93e0
commit ebcdca5a0d
13 changed files with 219 additions and 51 deletions

View File

@ -1013,7 +1013,7 @@ nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
#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;

View File

@ -437,7 +437,7 @@ GCZeal(JSContext *cx,
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

View File

@ -574,7 +574,7 @@ JetpackChild::GCZeal(JSContext* cx, uintN argc, jsval *vp)
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

View File

@ -2591,14 +2591,24 @@ JS_IsGCMarkingTracer(JSTracer *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)
@ -6122,9 +6132,19 @@ JS_ClearContextThread(JSContext *cx)
#ifdef JS_GC_ZEAL
JS_PUBLIC_API(void)
JS_SetGCZeal(JSContext *cx, uint8 zeal)
JS_SetGCZeal(JSContext *cx, uint8 zeal, uint32 frequency, JSBool compartment)
{
cx->runtime->gcZeal = zeal;
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

View File

@ -1766,6 +1766,9 @@ JS_DumpHeap(JSContext *cx, FILE *fp, void* startThing, uint32 startKind,
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);
@ -3864,8 +3867,13 @@ JS_NewObjectForConstructor(JSContext *cx, const jsval *vp);
#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

View File

@ -451,8 +451,43 @@ struct JSRuntime {
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;

View File

@ -1230,7 +1230,7 @@ SetCallArg(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
else
argp = &obj->callObjArg(i);
GC_POKE(cx, *argp);
GCPoke(cx, *argp);
*argp = *vp;
return true;
}
@ -1253,7 +1253,7 @@ SetCallUpvar(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
Value *up = &obj->getCallObjCallee()->getFlatClosureUpvar(i);
GC_POKE(cx, *up);
GCPoke(cx, *up);
*up = *vp;
return true;
}
@ -1310,7 +1310,7 @@ SetCallVar(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
else
varp = &obj->callObjVar(i);
GC_POKE(cx, *varp);
GCPoke(cx, *varp);
*varp = *vp;
return true;
}

View File

@ -1325,10 +1325,6 @@ inline bool
NeedLastDitchGC(JSContext *cx)
{
JSRuntime *rt = cx->runtime;
#ifdef JS_GC_ZEAL
if (rt->gcZeal >= 1)
return true;
#endif
return rt->gcIsNeeded;
}
@ -1358,6 +1354,12 @@ RunLastDitchGC(JSContext *cx)
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)
@ -1374,7 +1376,7 @@ RefillTypedFreeList(JSContext *cx, unsigned thingKind)
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) {
@ -1918,12 +1920,10 @@ 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. */
@ -1958,12 +1958,10 @@ 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) {
@ -2271,11 +2269,7 @@ MarkAndSweep(JSContext *cx, JSCompartment *comp, JSGCInvocationKind gckind GCTIM
* 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;
@ -2943,6 +2937,26 @@ NewCompartment(JSContext *cx, JSPrincipals *principals)
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 */

View File

@ -869,17 +869,6 @@ CheckAllocation(JSContext *cx);
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);
@ -1317,6 +1306,10 @@ 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 */

View File

@ -161,6 +161,27 @@ GetGCKindSlots(FinalizeKind thingKind)
}
}
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 */
@ -183,6 +204,11 @@ NewFinalizableGCThing(JSContext *cx, unsigned thingKind)
#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));

View File

@ -5847,7 +5847,7 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool str
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
@ -6527,7 +6527,7 @@ js_SetReservedSlot(JSContext *cx, JSObject *obj, uint32 slot, const Value &v)
}
obj->setSlot(slot, v);
GC_POKE(cx, JS_NULL);
GCPoke(cx, NullValue());
return true;
}

View File

@ -635,7 +635,12 @@ usage(void)
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");
@ -702,6 +707,25 @@ namespace js {
}
#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)
{
@ -785,7 +809,7 @@ ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc)
case 'Z':
if (++i == argc)
return usage();
JS_SetGCZeal(cx, !!(atoi(argv[i])));
ParseZealArg(cx, argv[i]);
break;
#endif
#ifdef DEBUG
@ -1468,8 +1492,15 @@ AssertJit(JSContext *cx, uintN argc, jsval *vp)
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",
@ -1565,11 +1596,46 @@ GCParameter(JSContext *cx, uintN argc, jsval *vp)
static JSBool
GCZeal(JSContext *cx, uintN argc, jsval *vp)
{
uint32 zeal;
uint32 zeal, frequency = JS_DEFAULT_ZEAL_FREQ;
JSBool compartment = JS_FALSE;
if (!JS_ValueToECMAUint32(cx, argc == 0 ? JSVAL_VOID : vp[2], &zeal))
if (argc > 3) {
JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_TOO_MANY_ARGS, "gczeal");
return JS_FALSE;
JS_SetGCZeal(cx, (uint8)zeal);
}
if (!JS_ValueToECMAUint32(cx, argc < 1 ? JSVAL_VOID : vp[2], &zeal))
return JS_FALSE;
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;
}
@ -4734,7 +4800,8 @@ static JSFunctionSpec shell_functions[] = {
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),
@ -4837,7 +4904,8 @@ static const char *const shell_help_messages[] = {
" 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
@ -4856,7 +4924,10 @@ static const char *const shell_help_messages[] = {
" 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",
@ -6046,6 +6117,7 @@ main(int argc, char **argv, char **envp)
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);

View File

@ -571,7 +571,7 @@ GCZeal(JSContext *cx, uintN argc, jsval *vp)
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;
}