Bug 1103957 - prevent phase nesting, r=terrence

This commit is contained in:
Steve Fink 2014-12-09 14:59:11 -08:00
parent fd6d2cd524
commit 90fd4d6a3f
3 changed files with 58 additions and 29 deletions

View File

@ -283,6 +283,13 @@ struct PhaseInfo
static const Phase PHASE_NO_PARENT = PHASE_LIMIT;
/*
* Note that PHASE_MUTATOR, PHASE_GC_BEGIN, and PHASE_GC_END never have any
* child phases. If beginPhase is called while one of these is active, they
* will automatically be suspended and resumed when the phase stack is next
* empty. Timings for these phases are thus exclusive of any other phase.
*/
static const PhaseInfo phases[] = {
{ PHASE_MUTATOR, "Mutator Running", PHASE_NO_PARENT },
{ PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT },
@ -639,11 +646,11 @@ Statistics::Statistics(JSRuntime *rt)
fullFormat(false),
gcDepth(0),
nonincrementalReason(nullptr),
timingMutator(false),
timedGCStart(0),
preBytes(0),
maxPauseInInterval(0),
phaseNestingDepth(0),
suspendedPhaseNestingDepth(0),
sliceCallback(nullptr)
{
PodArrayZero(phaseTotals);
@ -833,12 +840,10 @@ Statistics::endSlice()
void
Statistics::startTimingMutator()
{
MOZ_ASSERT(!timingMutator);
// Should only be called from outside of GC
MOZ_ASSERT(phaseNestingDepth == 0);
MOZ_ASSERT(suspendedPhaseNestingDepth == 0);
timingMutator = true;
timedGCTime = 0;
phaseStartTimes[PHASE_MUTATOR] = 0;
phaseTimes[PHASE_MUTATOR] = 0;
@ -847,38 +852,42 @@ Statistics::startTimingMutator()
beginPhase(PHASE_MUTATOR);
}
void
bool
Statistics::stopTimingMutator(double &mutator_ms, double &gc_ms)
{
MOZ_ASSERT(timingMutator);
// Should only be called from outside of GC
MOZ_ASSERT(phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR);
// This should only be called from outside of GC, while timing the mutator.
if (phaseNestingDepth != 1 || phaseNesting[0] != PHASE_MUTATOR)
return false;
endPhase(PHASE_MUTATOR);
mutator_ms = t(phaseTimes[PHASE_MUTATOR]);
gc_ms = t(timedGCTime);
timingMutator = false;
return true;
}
void
Statistics::beginPhase(Phase phase)
{
/* Guard against re-entry */
MOZ_ASSERT(!phaseStartTimes[phase]);
Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
if (timingMutator) {
if (phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR) {
endPhase(PHASE_MUTATOR);
timedGCStart = PRMJ_Now();
}
// Re-entry is allowed during callbacks. Do not account nested GC time
// against the callbacks.
//
// Reuse this mechanism for managing PHASE_MUTATOR.
if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
suspendedPhases[suspendedPhaseNestingDepth++] = parent;
MOZ_ASSERT(suspendedPhaseNestingDepth <= mozilla::ArrayLength(suspendedPhases));
endPhase(parent);
parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
}
// Guard against any other re-entry.
MOZ_ASSERT(!phaseStartTimes[phase]);
#ifdef DEBUG
MOZ_ASSERT(phases[phase].index == phase);
Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
// Major and minor GCs can nest inside PHASE_GC_BEGIN/PHASE_GC_END.
MOZ_ASSERT_IF(gcDepth == 1 && phase != PHASE_MINOR_GC, phases[phase].parent == parent);
#endif
@ -891,20 +900,31 @@ Statistics::beginPhase(Phase phase)
void
Statistics::endPhase(Phase phase)
{
int64_t now = PRMJ_Now();
if (phase == PHASE_MUTATOR)
timedGCStart = now;
phaseNestingDepth--;
int64_t now = PRMJ_Now();
int64_t t = now - phaseStartTimes[phase];
if (!slices.empty())
slices.back().phaseTimes[phase] += t;
phaseTimes[phase] += t;
phaseStartTimes[phase] = 0;
if (timingMutator) {
if (phaseNestingDepth == 0 && phase != PHASE_MUTATOR) {
// When emptying the stack, we may need to resume a callback phase
// (PHASE_GC_BEGIN/END) or if not, return to timing the mutator
// (PHASE_MUTATOR).
//
// However, if the phase we're ending is PHASE_MUTATOR, that means
// beginPhase is calling endPhase(PHASE_MUTATOR) because some other phase
// is starting. So don't resume any earlier phase.
if (phaseNestingDepth == 0 && suspendedPhaseNestingDepth > 0 && phase != PHASE_MUTATOR) {
Phase resumePhase = suspendedPhases[--suspendedPhaseNestingDepth];
if (resumePhase == PHASE_MUTATOR)
timedGCTime += now - timedGCStart;
beginPhase(PHASE_MUTATOR);
}
beginPhase(resumePhase);
}
}

View File

@ -125,7 +125,7 @@ struct Statistics
void endSlice();
void startTimingMutator();
void stopTimingMutator(double &mutator_ms, double &gc_ms);
bool stopTimingMutator(double &mutator_ms, double &gc_ms);
void reset(const char *reason) { slices.back().resetReason = reason; }
void nonincremental(const char *reason) { nonincrementalReason = reason; }
@ -188,9 +188,6 @@ struct Statistics
/* Most recent time when the given phase started. */
int64_t phaseStartTimes[PHASE_LIMIT];
/* Are we currently timing mutator vs GC time? */
bool timingMutator;
/* Bookkeeping for GC timings when timingMutator is true */
int64_t timedGCStart;
int64_t timedGCTime;
@ -215,6 +212,15 @@ struct Statistics
Phase phaseNesting[MAX_NESTING];
size_t phaseNestingDepth;
/*
* To avoid recursive nesting, we discontinue a callback phase when any
* other phases are started. Remember what phase to resume when the inner
* phases are complete. (And because GCs can nest within the callbacks any
* number of times, we need a whole stack of of phases to resume.)
*/
Phase suspendedPhases[MAX_NESTING];
size_t suspendedPhaseNestingDepth;
/* Sweep times for SCCs of compartments. */
Vector<int64_t, 0, SystemAllocPolicy> sccTimes;

View File

@ -1680,7 +1680,10 @@ StopTimingMutator(JSContext *cx, unsigned argc, jsval *vp)
}
double mutator_ms, gc_ms;
cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms);
if (!cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms)) {
JS_ReportError(cx, "stopTimingMutator called when not timing the mutator");
return false;
}
double total_ms = mutator_ms + gc_ms;
if (total_ms > 0) {
fprintf(gOutFile, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n",