Bug 1176880 part 1 - Add a flag on the Debugger & Compartment to record code-coverage information. r=shu

This commit is contained in:
Nicolas B. Pierron 2015-09-16 21:11:34 +02:00
Родитель 02eebef322
Коммит b2aebab5f5
14 изменённых файлов: 312 добавлений и 24 удалений

Просмотреть файл

@ -308,22 +308,6 @@ js::gc::GCRuntime::markRuntime(JSTracer* trc, TraceOrMarkRuntime traceOrMark)
for (ContextIter acx(rt); !acx.done(); acx.next())
acx->mark(trc);
for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
if (traceOrMark == MarkRuntime && !zone->isCollecting())
continue;
/* Do not discard scripts with counts while profiling. */
if (rt->profilingScripts && !rt->isHeapMinorCollecting()) {
for (ZoneCellIterUnderGC i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
JSScript* script = i.get<JSScript>();
if (script->hasScriptCounts()) {
TraceRoot(trc, &script, "profilingScripts");
MOZ_ASSERT(script == i.get<JSScript>());
}
}
}
}
for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
c->traceRoots(trc, traceOrMark);

Просмотреть файл

@ -0,0 +1,40 @@
// This script check that when we enable / disable the code coverage collection,
// then we have different results for the getOffsetsCoverage methods.
var g = newGlobal();
var dbg = Debugger(g);
var coverageInfo = [];
var num = 20;
function loop(i) {
var n = 0;
for (n = 0; n < i; n++)
debugger;
}
g.eval(loop.toSource());
dbg.onDebuggerStatement = function (f) {
// Collect coverage info each time we hit a debugger statement.
coverageInfo.push(f.callee.script.getOffsetsCoverage());
};
coverageInfo = [];
dbg.collectCoverageInfo = false;
g.eval("loop(" + num + ");");
assertEq(coverageInfo.length, num);
assertEq(coverageInfo[0], null);
assertEq(coverageInfo[num - 1], null);
coverageInfo = [];
dbg.collectCoverageInfo = true;
g.eval("loop(" + num + ");");
assertEq(coverageInfo.length, num);
assertEq(!coverageInfo[0], false);
assertEq(!coverageInfo[num - 1], false);
coverageInfo = [];
dbg.collectCoverageInfo = false;
g.eval("loop(" + num + ");");
assertEq(coverageInfo.length, num);
assertEq(coverageInfo[0], null);
assertEq(coverageInfo[num - 1], null);

Просмотреть файл

@ -0,0 +1,21 @@
// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack
var g = newGlobal();
var dbg = Debugger(g);
function loop(i) {
var n = 0;
for (n = 0; n < i; n++)
debugger;
}
g.eval(loop.toSource());
var countDown = 20;
dbg.onDebuggerStatement = function (f) {
// Should throw an error.
if (countDown > 0 && --countDown == 0) {
dbg.collectCoverageInfo = !dbg.collectCoverageInfo;
}
};
dbg.collectCoverageInfo = false;
g.eval("loop("+ (2 * countDown) +");");

Просмотреть файл

@ -0,0 +1,22 @@
// |jit-test| error: Error: can't start debugging: a debuggee script is on the stack
var g = newGlobal();
var dbg = Debugger(g);
function loop(i) {
var n = 0;
for (n = 0; n < i; n++)
debugger;
}
g.eval(loop.toSource());
var countDown = 20;
dbg.onDebuggerStatement = function (f) {
// Should throw an error.
if (countDown > 0 && --countDown == 0) {
dbg.collectCoverageInfo = !dbg.collectCoverageInfo;
}
};
dbg.collectCoverageInfo = true;
g.eval("loop("+ (2 * countDown) +");");

Просмотреть файл

@ -0,0 +1,24 @@
var g = newGlobal();
var dbg = Debugger(g);
function f(x) {
while (x) {
interruptIf(true);
x -= 1;
}
}
g.eval(f.toSource());
// Toogle the debugger while the function f is running.
setInterruptCallback(toogleDebugger);
function toogleDebugger() {
dbg.enabled = !dbg.enabled;
return true;
}
dbg.collectCoverageInfo = false;
dbg.enabled = false;
g.eval("f(10);");
dbg.collectCoverageInfo = true;
dbg.enabled = false;
g.eval("f(10);");

Просмотреть файл

@ -91,6 +91,12 @@ BaselineCompiler::compile()
if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
return Method_Error;
// When a Debugger set the collectCoverageInfo flag, we recompile baseline
// scripts without entering the interpreter again. We have to create the
// ScriptCounts if they do not exist.
if (!script->hasScriptCounts() && cx->compartment()->collectCoverage())
script->initScriptCounts(cx);
// Pin analysis info during compilation.
AutoEnterAnalysis autoEnterAnalysis(cx);

Просмотреть файл

@ -568,6 +568,16 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t
if (objectMetadataTable)
objectMetadataTable->trace(trc);
if (scriptCountsMap && !trc->runtime()->isHeapMinorCollecting()) {
MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage());
for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
JSScript* script = const_cast<JSScript*>(r.front().key());
MOZ_ASSERT(script->hasScriptCounts());
TraceRoot(trc, &script, "profilingScripts");
MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
}
}
}
void
@ -925,14 +935,15 @@ JSCompartment::updateDebuggerObservesFlag(unsigned flag)
{
MOZ_ASSERT(isDebuggee());
MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
flag == DebuggerObservesCoverage ||
flag == DebuggerObservesAsmJS);
const GlobalObject::DebuggerVector* v = maybeGlobal()->getDebuggers();
for (Debugger * const* p = v->begin(); p != v->end(); p++) {
Debugger* dbg = *p;
if (flag == DebuggerObservesAllExecution
? dbg->observesAllExecution()
: dbg->observesAsmJS())
if (flag == DebuggerObservesAllExecution ? dbg->observesAllExecution() :
flag == DebuggerObservesCoverage ? dbg->observesCoverage() :
dbg->observesAsmJS())
{
debugModeBits |= flag;
return;
@ -951,6 +962,49 @@ JSCompartment::unsetIsDebuggee()
}
}
void
JSCompartment::updateDebuggerObservesCoverage()
{
bool previousState = debuggerObservesCoverage();
updateDebuggerObservesFlag(DebuggerObservesCoverage);
if (previousState == debuggerObservesCoverage())
return;
if (debuggerObservesCoverage()) {
// Interrupt any running interpreter frame. The scriptCounts are
// allocated on demand when a script resume its execution.
for (ActivationIterator iter(runtimeFromMainThread()); !iter.done(); ++iter) {
if (iter->isInterpreter())
iter->asInterpreter()->enableInterruptsUnconditionally();
}
return;
}
// If the runtime flag is enabled, then keep the data until
// StopPCCountProfiling is called.
if (runtimeFromMainThread()->profilingScripts)
return;
clearScriptCounts();
}
void
JSCompartment::clearScriptCounts()
{
if (!scriptCountsMap)
return;
// Clear all hasScriptCounts_ flags of JSScript, in order to release all
// ScriptCounts entry of the current compartment.
for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
ScriptCounts* value = &r.front().value();
r.front().key()->takeOverScriptCountsMapEntry(value);
}
js_delete(scriptCountsMap);
scriptCountsMap = nullptr;
}
void
JSCompartment::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler)
{

Просмотреть файл

@ -465,13 +465,15 @@ struct JSCompartment
IsDebuggee = 1 << 0,
DebuggerObservesAllExecution = 1 << 1,
DebuggerObservesAsmJS = 1 << 2,
DebuggerNeedsDelazification = 1 << 3
DebuggerObservesCoverage = 1 << 3,
DebuggerNeedsDelazification = 1 << 4
};
unsigned debugModeBits;
static const unsigned DebuggerObservesMask = IsDebuggee |
DebuggerObservesAllExecution |
DebuggerObservesCoverage |
DebuggerObservesAsmJS;
void updateDebuggerObservesFlag(unsigned flag);
@ -652,6 +654,22 @@ struct JSCompartment
updateDebuggerObservesFlag(DebuggerObservesAsmJS);
}
// True if this compartment's global is a debuggee of some Debugger object
// whose collectCoverageInfo flag is true.
bool debuggerObservesCoverage() const {
static const unsigned Mask = DebuggerObservesCoverage;
return (debugModeBits & Mask) == Mask;
}
void updateDebuggerObservesCoverage();
// The code coverage can be enabled either for each compartment, with the
// Debugger API, or for the entire runtime.
bool collectCoverage() const {
return debuggerObservesCoverage() ||
runtimeFromAnyThread()->profilingScripts;
}
void clearScriptCounts();
bool needsDelazificationForDebugger() const {
return debugModeBits & DebuggerNeedsDelazification;
}

Просмотреть файл

@ -1455,6 +1455,16 @@ JSScript::getIonCounts()
return getScriptCounts().ionCounts_;
}
void
JSScript::takeOverScriptCountsMapEntry(ScriptCounts* entryValue)
{
#ifdef DEBUG
ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this);
MOZ_ASSERT(entryValue == &p->value());
#endif
hasScriptCounts_ = false;
}
void
JSScript::releaseScriptCounts(ScriptCounts* counts)
{

Просмотреть файл

@ -1639,6 +1639,8 @@ class JSScript : public js::gc::TenuredCell
js::jit::IonScriptCounts* getIonCounts();
void releaseScriptCounts(js::ScriptCounts* counts);
void destroyScriptCounts(js::FreeOp* fop);
// The entry should be removed after using this function.
void takeOverScriptCountsMapEntry(js::ScriptCounts* entryValue);
jsbytecode* main() {
return code() + mainOffset();

Просмотреть файл

@ -358,6 +358,7 @@ Debugger::Debugger(JSContext* cx, NativeObject* dbg)
uncaughtExceptionHook(nullptr),
enabled(true),
allowUnobservedAsmJS(false),
collectCoverageInfo(false),
observedGCs(cx),
tenurePromotionsLog(cx),
trackingTenurePromotions(false),
@ -1841,6 +1842,9 @@ class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObser
bool init() { return compartments_.init() && zones_.init(); }
bool add(JSCompartment* comp) { return compartments_.put(comp) && zones_.put(comp->zone()); }
typedef HashSet<JSCompartment*>::Range CompartmentRange;
const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
const HashSet<Zone*>* zones() const { return &zones_; }
bool shouldRecompileOrInvalidate(JSScript* script) const {
return script->hasBaselineScript() && compartments_.has(script->compartment());
@ -2185,6 +2189,14 @@ Debugger::observesAsmJS() const
return NotObserving;
}
Debugger::IsObserving
Debugger::observesCoverage() const
{
if (enabled && collectCoverageInfo)
return Observing;
return NotObserving;
}
// Toggle whether this Debugger's debuggees observe all execution. This is
// called when a hook that observes all execution is set or unset. See
// hookObservesAllExecution.
@ -2213,6 +2225,53 @@ Debugger::updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving obser
return updateExecutionObservability(cx, obs, observing);
}
bool
Debugger::updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing)
{
ExecutionObservableCompartments obs(cx);
if (!obs.init())
return false;
for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
GlobalObject* global = r.front();
JSCompartment* comp = global->compartment();
if (comp->debuggerObservesCoverage() == observing)
continue;
// Invalidate and recompile a compartment to add or remove PCCounts
// increments. We have to eagerly invalidate, as otherwise we might have
// dangling pointers to freed PCCounts.
if (!obs.add(comp))
return false;
}
// If any frame on the stack belongs to the debuggee, then we cannot update
// the ScriptCounts, because this would imply to invalidate a Debugger.Frame
// to recompile it with/without ScriptCount support.
for (ScriptFrameIter iter(cx, ScriptFrameIter::ALL_CONTEXTS,
ScriptFrameIter::GO_THROUGH_SAVED);
!iter.done();
++iter)
{
if (obs.shouldMarkAsDebuggee(iter)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
return false;
}
}
if (!updateExecutionObservability(cx, obs, observing))
return false;
// All compartments can safely be toggled, and all scripts will be
// recompiled. Thus we can update each compartment accordingly.
typedef ExecutionObservableCompartments::CompartmentRange CompartmentRange;
for (CompartmentRange r = obs.compartments()->all(); !r.empty(); r.popFront())
r.front()->updateDebuggerObservesCoverage();
return true;
}
void
Debugger::updateObservesAsmJSOnDebuggees(IsObserving observing)
{
@ -2694,6 +2753,9 @@ Debugger::setEnabled(JSContext* cx, unsigned argc, Value* vp)
if (!dbg->updateObservesAllExecutionOnDebuggees(cx, dbg->observesAllExecution()))
return false;
// Note: To toogle code coverage, we currently need to have no live
// stack frame, thus the coverage does not depend on the enabled flag.
dbg->updateObservesAsmJSOnDebuggees(dbg->observesAsmJS());
}
@ -2902,6 +2964,30 @@ Debugger::setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/* static */ bool
Debugger::getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "get collectCoverageInfo", args, dbg);
args.rval().setBoolean(dbg->collectCoverageInfo);
return true;
}
/* static */ bool
Debugger::setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "set collectCoverageInfo", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.set collectCoverageInfo", 1))
return false;
dbg->collectCoverageInfo = ToBoolean(args[0]);
IsObserving observing = dbg->collectCoverageInfo ? Observing : NotObserving;
if (!dbg->updateObservesCoverageOnDebuggees(cx, observing))
return false;
args.rval().setUndefined();
return true;
}
/* static */ bool
Debugger::getMemory(JSContext* cx, unsigned argc, Value* vp)
{
@ -3339,6 +3425,7 @@ Debugger::addDebuggeeGlobal(JSContext* cx, Handle<GlobalObject*> global)
// (6)
debuggeeCompartment->setIsDebuggee();
debuggeeCompartment->updateDebuggerObservesAsmJS();
debuggeeCompartment->updateDebuggerObservesCoverage();
if (observesAllExecution() && !ensureExecutionObservabilityOfCompartment(cx, debuggeeCompartment))
return false;
@ -3453,6 +3540,7 @@ Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
} else {
global->compartment()->updateDebuggerObservesAllExecution();
global->compartment()->updateDebuggerObservesAsmJS();
global->compartment()->updateDebuggerObservesCoverage();
}
}
@ -4433,6 +4521,8 @@ const JSPropertySpec Debugger::properties[] = {
Debugger::setUncaughtExceptionHook, 0),
JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
Debugger::setAllowUnobservedAsmJS, 0),
JS_PSGS("collectCoverageInfo", Debugger::getCollectCoverageInfo,
Debugger::setCollectCoverageInfo, 0),
JS_PSG("memory", Debugger::getMemory, 0),
JS_PSGS("onIonCompilation", Debugger::getOnIonCompilation, Debugger::setOnIonCompilation, 0),
JS_PS_END

Просмотреть файл

@ -340,6 +340,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
bool enabled;
bool allowUnobservedAsmJS;
// Wether to enable code coverage on the Debuggee.
bool collectCoverageInfo;
JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
// The set of GC numbers for which one or more of this Debugger's observed
@ -545,6 +549,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
static bool getCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
static bool setCollectCoverageInfo(JSContext* cx, unsigned argc, Value* vp);
static bool getMemory(JSContext* cx, unsigned argc, Value* vp);
static bool getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
static bool setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
@ -594,6 +600,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
// execution of its debuggees.
IsObserving observesAsmJS() const;
// Whether the Debugger instance needs to observe coverage of any JavaScript
// execution.
IsObserving observesCoverage() const;
private:
static bool ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame);
static bool ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp);
@ -601,6 +611,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool hookObservesAllExecution(Hook which);
bool updateObservesAllExecutionOnDebuggees(JSContext* cx, IsObserving observing);
bool updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing);
void updateObservesAsmJSOnDebuggees(IsObserving observing);
JSObject* getHook(Hook hook) const;

Просмотреть файл

@ -1960,7 +1960,7 @@ Interpret(JSContext* cx, RunState& state)
MOZ_CRASH("bad Debugger::onEnterFrame status");
}
if (cx->runtime()->profilingScripts)
if (cx->compartment()->collectCoverage())
activation.enableInterruptsUnconditionally();
// Enter the interpreter loop starting at the current pc.
@ -1973,9 +1973,8 @@ CASE(EnableInterruptsPseudoOpcode)
bool moreInterrupts = false;
jsbytecode op = *REGS.pc;
if (cx->runtime()->profilingScripts) {
if (!script->hasScriptCounts())
script->initScriptCounts(cx);
if (!script->hasScriptCounts() && cx->compartment()->collectCoverage()) {
script->initScriptCounts(cx);
moreInterrupts = true;
}

Просмотреть файл

@ -379,6 +379,13 @@ JSRuntime::~JSRuntime()
wpmap->clear();
}
/*
* Clear script counts map, to remove the strong reference on the
* JSScript key.
*/
for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next())
comp->clearScriptCounts();
/* Clear atoms to remove GC roots and heap allocations. */
finishAtoms();