Bug 1117768 - Fix assertion in AutoStopVerifyingBarriers and add tests, r=terrence

--HG--
extra : rebase_source : 105c0170ed76a94a5042bbdb428f6d43054933b9
This commit is contained in:
Steve Fink 2015-01-12 08:34:00 -08:00
parent 3d42c13c85
commit e40c6c6f27
5 changed files with 215 additions and 3 deletions

View File

@ -118,7 +118,9 @@ class AutoStopVerifyingBarriers
gcstats::Phase outer = gc->stats.currentPhase();
if (outer != gcstats::PHASE_NONE)
gc->stats.endPhase(outer);
MOZ_ASSERT(gc->stats.currentPhase() == gcstats::PHASE_NONE);
MOZ_ASSERT((gc->stats.currentPhase() == gcstats::PHASE_NONE) ||
(gc->stats.currentPhase() == gcstats::PHASE_GC_BEGIN) ||
(gc->stats.currentPhase() == gcstats::PHASE_GC_END));
if (restartPreVerifier)
gc->startVerifyPreBarriers();

View File

@ -1066,8 +1066,8 @@ Statistics::beginPhase(Phase phase)
//
// Reuse this mechanism for managing PHASE_MUTATOR.
if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
MOZ_ASSERT(suspendedPhaseNestingDepth < mozilla::ArrayLength(suspendedPhases));
suspendedPhases[suspendedPhaseNestingDepth++] = parent;
MOZ_ASSERT(suspendedPhaseNestingDepth <= mozilla::ArrayLength(suspendedPhases));
recordPhaseEnd(parent);
parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
}

View File

@ -195,6 +195,8 @@ struct Statistics
return phaseNesting[phaseNestingDepth - 1];
}
static const size_t MAX_NESTING = 20;
private:
JSRuntime *runtime;
@ -257,7 +259,6 @@ struct Statistics
int64_t maxPauseInInterval;
/* Phases that are currently on stack. */
static const size_t MAX_NESTING = 8;
Phase phaseNesting[MAX_NESTING];
size_t phaseNestingDepth;
size_t activeDagSlot;

View File

@ -1654,6 +1654,149 @@ Quit(JSContext *cx, unsigned argc, jsval *vp)
return false;
}
namespace gcCallback {
struct MajorGC {
int32_t depth;
int32_t phases;
};
static void
majorGC(JSRuntime *rt, JSGCStatus status, void *data)
{
auto info = static_cast<MajorGC*>(data);
if (!(info->phases & (1 << status)))
return;
if (info->depth > 0) {
info->depth--;
PrepareForFullGC(rt);
JS::GCForReason(rt, GC_NORMAL, gcreason::API);
info->depth++;
}
}
struct MinorGC {
int32_t phases;
bool active;
};
static void
minorGC(JSRuntime *rt, JSGCStatus status, void *data)
{
auto info = static_cast<MinorGC*>(data);
if (!(info->phases & (1 << status)))
return;
if (info->active) {
info->active = false;
rt->gc.evictNursery(gcreason::DEBUG_GC);
info->active = true;
}
}
// Process global, should really be runtime-local. Also, the final one of these
// is currently leaked, since they are only deleted when changing.
MajorGC *prevMajorGC = nullptr;
MinorGC *prevMinorGC = nullptr;
} /* namespace gcCallback */
static bool
SetGCCallback(JSContext *cx, unsigned argc, jsval *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) {
JS_ReportError(cx, "Wrong number of arguments");
return false;
}
RootedObject opts(cx);
if (!JS_ValueToObject(cx, args[0], &opts))
return false;
RootedValue v(cx);
if (!JS_GetProperty(cx, opts, "action", &v))
return false;
JSString *str = JS::ToString(cx, v);
if (!str)
return false;
JSAutoByteString action(cx, str);
if (!action)
return false;
int32_t phases = 0;
if ((strcmp(action.ptr(), "minorGC") == 0) || (strcmp(action.ptr(), "majorGC") == 0)) {
if (!JS_GetProperty(cx, opts, "phases", &v))
return false;
if (v.isUndefined()) {
phases = (1 << JSGC_END);
} else {
JSString *str = JS::ToString(cx, v);
if (!str)
return false;
JSAutoByteString phasesStr(cx, str);
if (!phasesStr)
return false;
if (strcmp(phasesStr.ptr(), "begin") == 0)
phases = (1 << JSGC_BEGIN);
else if (strcmp(phasesStr.ptr(), "end") == 0)
phases = (1 << JSGC_END);
else if (strcmp(phasesStr.ptr(), "both") == 0)
phases = (1 << JSGC_BEGIN) | (1 << JSGC_END);
else {
JS_ReportError(cx, "Invalid callback phase");
return false;
}
}
}
if (gcCallback::prevMajorGC) {
JS_SetGCCallback(cx->runtime(), nullptr, nullptr);
js_delete<gcCallback::MajorGC>(gcCallback::prevMajorGC);
gcCallback::prevMajorGC = nullptr;
}
if (gcCallback::prevMinorGC) {
JS_SetGCCallback(cx->runtime(), nullptr, nullptr);
js_delete<gcCallback::MinorGC>(gcCallback::prevMinorGC);
gcCallback::prevMinorGC = nullptr;
}
if (strcmp(action.ptr(), "minorGC") == 0) {
auto info = js_new<gcCallback::MinorGC>();
info->phases = phases;
info->active = true;
JS_SetGCCallback(cx->runtime(), gcCallback::minorGC, info);
} else if (strcmp(action.ptr(), "majorGC") == 0) {
if (!JS_GetProperty(cx, opts, "depth", &v))
return false;
int32_t depth = 1;
if (!v.isUndefined()) {
if (!ToInt32(cx, v, &depth))
return false;
}
if (depth > int32_t(gcstats::Statistics::MAX_NESTING - 4)) {
JS_ReportError(cx, "Nesting depth too large, would overflow");
return false;
}
auto info = js_new<gcCallback::MajorGC>();
info->phases = phases;
info->depth = depth;
JS_SetGCCallback(cx->runtime(), gcCallback::majorGC, info);
} else {
JS_ReportError(cx, "Unknown GC callback action");
return false;
}
args.rval().setUndefined();
return true;
}
static bool
StartTimingMutator(JSContext *cx, unsigned argc, jsval *vp)
{
@ -4189,6 +4332,12 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
" Throw if the first two arguments are not the same (both +0 or both -0,\n"
" both NaN, or non-zero and ===)."),
JS_FN_HELP("setGCCallback", SetGCCallback, 1, 0,
"setGCCallback({action:\"...\", options...})",
" Set the GC callback. action may be:\n"
" 'minorGC' - run a nursery collection\n"
" 'majorGC' - run a major collection, nesting up to a given 'depth'\n"),
JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0,
"startTimingMutator()",
" Start accounting time to mutator vs GC."),

View File

@ -0,0 +1,60 @@
// |reftest| skip-if(!xulRuntime.shell||xulRuntime.OS=="WINNT")
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
function garbage() {
var x;
for (var i = 0; i < 100000; i++)
x = { 'i': i };
}
setGCCallback({
action: "majorGC",
depth: 1,
phases: "both"
});
gc();
garbage();
setGCCallback({
action: "majorGC",
depth: 2,
phases: "both"
});
gc();
garbage();
setGCCallback({
action: "majorGC",
depth: 10,
phases: "begin"
});
gc();
garbage();
setGCCallback({
action: "minorGC",
phases: "both"
});
gc();
garbage();
var caught = false;
try {
setGCCallback({
action: "majorGC",
depth: 10000,
phases: "begin"
});
} catch (e) {
caught = ((""+e).indexOf("Nesting depth too large") >= 0);
}
reportCompare(caught, true);