Bug 716647 - Part 5: Relax the no on-stack scripts restriction for addDebuggee. (r=jimb)

This commit is contained in:
Shu-yu Guo 2014-04-24 01:59:37 -07:00
parent 6e13279fe5
commit e31d8af3d3
10 changed files with 337 additions and 102 deletions

View File

@ -12,7 +12,9 @@
#include "jit/IonSpewer.h" #include "jit/IonSpewer.h"
#include "jit/Recover.h" #include "jit/Recover.h"
#include "jit/RematerializedFrame.h" #include "jit/RematerializedFrame.h"
#include "vm/ArgumentsObject.h" #include "vm/ArgumentsObject.h"
#include "vm/Debugger.h"
#include "vm/TraceLogging.h" #include "vm/TraceLogging.h"
#include "jsscriptinlines.h" #include "jsscriptinlines.h"
@ -1536,7 +1538,7 @@ HandleBaselineInfoBailout(JSContext *cx, JSScript *outerScript, JSScript *innerS
return Invalidate(cx, outerScript); return Invalidate(cx, outerScript);
} }
static void static bool
CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size_t inlineDepth, CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size_t inlineDepth,
BaselineFrame *frame) BaselineFrame *frame)
{ {
@ -1545,7 +1547,7 @@ CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size
// We might not have rematerialized a frame if the user never requested a // We might not have rematerialized a frame if the user never requested a
// Debugger.Frame for it. // Debugger.Frame for it.
if (!rematFrame) if (!rematFrame)
return; return true;
MOZ_ASSERT(rematFrame->script() == frame->script()); MOZ_ASSERT(rematFrame->script() == frame->script());
MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs()); MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs());
@ -1562,6 +1564,11 @@ CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size
IonSpew(IonSpew_BaselineBailouts, IonSpew(IonSpew_BaselineBailouts,
" Copied from rematerialized frame at (%p,%u)", " Copied from rematerialized frame at (%p,%u)",
fp, inlineDepth); fp, inlineDepth);
if (cx->compartment()->debugMode())
return Debugger::handleIonBailout(cx, rematFrame, frame);
return true;
} }
uint32_t uint32_t
@ -1661,9 +1668,11 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo)
JitFrameIterator iter(cx); JitFrameIterator iter(cx);
size_t inlineDepth = numFrames; size_t inlineDepth = numFrames;
while (inlineDepth > 0) { while (inlineDepth > 0) {
if (iter.isBaselineJS()) { if (iter.isBaselineJS() &&
inlineDepth--; !CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
CopyFromRematerializedFrame(cx, act, outerFp, inlineDepth, iter.baselineFrame()); iter.baselineFrame()))
{
return false;
} }
++iter; ++iter;
} }

View File

@ -3036,6 +3036,28 @@ jit::RematerializeAllFrames(JSContext *cx, JSCompartment *comp)
return true; return true;
} }
bool
jit::UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
AutoDebugModeInvalidation &invalidate)
{
MOZ_ASSERT(invalidate.isFor(comp));
// Schedule invalidation of all optimized JIT code since debug mode
// invalidates assumptions.
invalidate.scheduleInvalidation(comp->debugMode());
// Recompile on-stack baseline scripts if we have a cx.
if (maybecx) {
IonContext ictx(maybecx, nullptr);
if (!RecompileOnStackBaselineScriptsForDebugMode(maybecx, comp)) {
js_ReportOutOfMemory(maybecx);
return false;
}
}
return true;
}
AutoDebugModeInvalidation::~AutoDebugModeInvalidation() AutoDebugModeInvalidation::~AutoDebugModeInvalidation()
{ {
MOZ_ASSERT(!!comp_ != !!zone_); MOZ_ASSERT(!!comp_ != !!zone_);
@ -3054,11 +3076,13 @@ AutoDebugModeInvalidation::~AutoDebugModeInvalidation()
StopAllOffThreadCompilations(comp); StopAllOffThreadCompilations(comp);
} }
// Don't discard active baseline scripts. They are recompiled for debug
// mode.
jit::MarkActiveBaselineScripts(zone); jit::MarkActiveBaselineScripts(zone);
for (JitActivationIterator iter(rt); !iter.done(); ++iter) { for (JitActivationIterator iter(rt); !iter.done(); ++iter) {
JSCompartment *comp = iter.activation()->compartment(); JSCompartment *comp = iter.activation()->compartment();
if ((comp_ && comp_ == comp) || (zone_ && zone_ == comp->zone())) { if (comp_ == comp || zone_ == comp->zone()) {
IonContext ictx(CompileRuntime::get(rt)); IonContext ictx(CompileRuntime::get(rt));
AutoFlushCache afc("AutoDebugModeInvalidation", rt->jitRuntime()); AutoFlushCache afc("AutoDebugModeInvalidation", rt->jitRuntime());
IonSpew(IonSpew_Invalidate, "Invalidating frames for debug mode toggle"); IonSpew(IonSpew_Invalidate, "Invalidating frames for debug mode toggle");
@ -3068,7 +3092,7 @@ AutoDebugModeInvalidation::~AutoDebugModeInvalidation()
for (gc::CellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) { for (gc::CellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
JSScript *script = i.get<JSScript>(); JSScript *script = i.get<JSScript>();
if ((comp_ && script->compartment() == comp_) || zone_) { if (script->compartment() == comp_ || zone_) {
FinishInvalidation<SequentialExecution>(fop, script); FinishInvalidation<SequentialExecution>(fop, script);
FinishInvalidation<ParallelExecution>(fop, script); FinishInvalidation<ParallelExecution>(fop, script);
FinishDiscardBaselineScript(fop, script); FinishDiscardBaselineScript(fop, script);

View File

@ -189,6 +189,8 @@ void TraceIonScripts(JSTracer* trc, JSScript *script);
void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode); void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode);
bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp); bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp);
bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
AutoDebugModeInvalidation &invalidate);
} // namespace jit } // namespace jit
} // namespace js } // namespace js

View File

@ -766,16 +766,13 @@ JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidatio
bool enabledBefore = debugMode(); bool enabledBefore = debugMode();
bool enabledAfter = (debugModeBits & DebugModeFromMask & ~DebugFromC) || b; bool enabledAfter = (debugModeBits & DebugModeFromMask & ~DebugFromC) || b;
// Debug mode can be enabled only when no scripts from the target // Enabling debug mode from C (vs of from JS) can only be done when no
// compartment are on the stack. It would even be incorrect to discard just // scripts from the target compartment are on the stack.
// the non-live scripts' JITScripts because they might share ICs with live
// scripts (bug 632343).
// //
// We do allow disabling debug mode while scripts are on the stack. In // We do allow disabling debug mode while scripts are on the stack. In
// that case the debug-mode code for those scripts remains, so subsequently // that case the debug-mode code for those scripts remains, so subsequently
// hooks may be called erroneously, even though debug mode is supposedly // hooks may be called erroneously, even though debug mode is supposedly
// off, and we have to live with it. // off, and we have to live with it.
//
bool onStack = false; bool onStack = false;
if (enabledBefore != enabledAfter) { if (enabledBefore != enabledAfter) {
onStack = hasScriptsOnStack(); onStack = hasScriptsOnStack();
@ -788,36 +785,28 @@ JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidatio
debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0); debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0);
JS_ASSERT(debugMode() == enabledAfter); JS_ASSERT(debugMode() == enabledAfter);
if (enabledBefore != enabledAfter) { if (enabledBefore != enabledAfter) {
updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate); // Pass in a nullptr cx to not bother recompiling for JSD1, since
// we're still enforcing the idle-stack invariant here.
if (!updateJITForDebugMode(nullptr, invalidate))
return false;
if (!enabledAfter) if (!enabledAfter)
DebugScopes::onCompartmentLeaveDebugMode(this); DebugScopes::onCompartmentLeaveDebugMode(this);
} }
return true; return true;
} }
void bool
JSCompartment::updateForDebugMode(FreeOp *fop, AutoDebugModeInvalidation &invalidate) JSCompartment::updateJITForDebugMode(JSContext *maybecx, AutoDebugModeInvalidation &invalidate)
{ {
JSRuntime *rt = runtimeFromMainThread();
for (ContextIter acx(rt); !acx.done(); acx.next()) {
if (acx->compartment() == this)
acx->updateJITEnabled();
}
#ifdef JS_ION #ifdef JS_ION
MOZ_ASSERT(invalidate.isFor(this));
JS_ASSERT_IF(debugMode(), !hasScriptsOnStack());
// Invalidate all JIT code since debug mode invalidates assumptions made
// by the JIT.
//
// The AutoDebugModeInvalidation argument makes sure we can't forget to // The AutoDebugModeInvalidation argument makes sure we can't forget to
// invalidate, but it is also important not to run any scripts in this // invalidate, but it is also important not to run any scripts in this
// compartment until the invalidate is destroyed. That is the caller's // compartment until the invalidate is destroyed. That is the caller's
// responsibility. // responsibility.
invalidate.scheduleInvalidation(debugMode()); if (!jit::UpdateForDebugMode(maybecx, this, invalidate))
return false;
#endif #endif
return true;
} }
bool bool
@ -840,25 +829,47 @@ JSCompartment::addDebuggee(JSContext *cx,
return false; return false;
} }
debugModeBits |= DebugFromJS; debugModeBits |= DebugFromJS;
if (!wasEnabled) if (!wasEnabled && !updateJITForDebugMode(cx, invalidate))
updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate); return false;
return true; return true;
} }
void bool
JSCompartment::removeDebuggee(FreeOp *fop, JSCompartment::removeDebuggee(JSContext *cx,
js::GlobalObject *global, js::GlobalObject *global,
js::GlobalObjectSet::Enum *debuggeesEnum) js::GlobalObjectSet::Enum *debuggeesEnum)
{ {
AutoDebugModeInvalidation invalidate(this); AutoDebugModeInvalidation invalidate(this);
return removeDebuggee(fop, global, invalidate, debuggeesEnum); return removeDebuggee(cx, global, invalidate, debuggeesEnum);
} }
void bool
JSCompartment::removeDebuggee(FreeOp *fop, JSCompartment::removeDebuggee(JSContext *cx,
js::GlobalObject *global, js::GlobalObject *global,
AutoDebugModeInvalidation &invalidate, AutoDebugModeInvalidation &invalidate,
js::GlobalObjectSet::Enum *debuggeesEnum) js::GlobalObjectSet::Enum *debuggeesEnum)
{
bool wasEnabled = debugMode();
removeDebuggeeUnderGC(cx->runtime()->defaultFreeOp(), global, invalidate, debuggeesEnum);
if (wasEnabled && !debugMode() && !updateJITForDebugMode(cx, invalidate))
return false;
return true;
}
void
JSCompartment::removeDebuggeeUnderGC(FreeOp *fop,
js::GlobalObject *global,
js::GlobalObjectSet::Enum *debuggeesEnum)
{
AutoDebugModeInvalidation invalidate(this);
removeDebuggeeUnderGC(fop, global, invalidate, debuggeesEnum);
}
void
JSCompartment::removeDebuggeeUnderGC(FreeOp *fop,
js::GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
js::GlobalObjectSet::Enum *debuggeesEnum)
{ {
bool wasEnabled = debugMode(); bool wasEnabled = debugMode();
JS_ASSERT(debuggees.has(global)); JS_ASSERT(debuggees.has(global));
@ -869,10 +880,8 @@ JSCompartment::removeDebuggee(FreeOp *fop,
if (debuggees.empty()) { if (debuggees.empty()) {
debugModeBits &= ~DebugFromJS; debugModeBits &= ~DebugFromJS;
if (wasEnabled && !debugMode()) { if (wasEnabled && !debugMode())
DebugScopes::onCompartmentLeaveDebugMode(this); DebugScopes::onCompartmentLeaveDebugMode(this);
updateForDebugMode(fop, invalidate);
}
} }
} }

View File

@ -395,18 +395,23 @@ struct JSCompartment
private: private:
/* This is called only when debugMode() has just toggled. */ /* This is called only when debugMode() has just toggled. */
void updateForDebugMode(js::FreeOp *fop, js::AutoDebugModeInvalidation &invalidate); bool updateJITForDebugMode(JSContext *maybecx, js::AutoDebugModeInvalidation &invalidate);
public: public:
js::GlobalObjectSet &getDebuggees() { return debuggees; } js::GlobalObjectSet &getDebuggees() { return debuggees; }
bool addDebuggee(JSContext *cx, js::GlobalObject *global); bool addDebuggee(JSContext *cx, js::GlobalObject *global);
bool addDebuggee(JSContext *cx, js::GlobalObject *global, bool addDebuggee(JSContext *cx, js::GlobalObject *global,
js::AutoDebugModeInvalidation &invalidate); js::AutoDebugModeInvalidation &invalidate);
void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global, bool removeDebuggee(JSContext *cx, js::GlobalObject *global,
js::GlobalObjectSet::Enum *debuggeesEnum = nullptr); js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global, bool removeDebuggee(JSContext *cx, js::GlobalObject *global,
js::AutoDebugModeInvalidation &invalidate, js::AutoDebugModeInvalidation &invalidate,
js::GlobalObjectSet::Enum *debuggeesEnum = nullptr); js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
void removeDebuggeeUnderGC(js::FreeOp *fop, js::GlobalObject *global,
js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
void removeDebuggeeUnderGC(js::FreeOp *fop, js::GlobalObject *global,
js::AutoDebugModeInvalidation &invalidate,
js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
bool setDebugModeFromC(JSContext *cx, bool b, bool setDebugModeFromC(JSContext *cx, bool b,
js::AutoDebugModeInvalidation &invalidate); js::AutoDebugModeInvalidation &invalidate);

View File

@ -1655,8 +1655,12 @@ Debugger::sweepAll(FreeOp *fop)
* might be GC'd too. Since detaching requires access to both * might be GC'd too. Since detaching requires access to both
* objects, this must be done before finalize time. * objects, this must be done before finalize time.
*/ */
for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
dbg->removeDebuggeeGlobal(fop, e.front(), nullptr, &e); // We can't recompile on-stack scripts here, and we
// can only toggle debug mode to off, so we use an
// infallible variant of removeDebuggeeGlobal.
dbg->removeDebuggeeGlobalUnderGC(fop, e.front(), nullptr, &e);
}
} }
} }
@ -1665,8 +1669,10 @@ Debugger::sweepAll(FreeOp *fop)
GlobalObjectSet &debuggees = comp->getDebuggees(); GlobalObjectSet &debuggees = comp->getDebuggees();
for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) { for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
GlobalObject *global = e.front(); GlobalObject *global = e.front();
if (IsObjectAboutToBeFinalized(&global)) if (IsObjectAboutToBeFinalized(&global)) {
// See infallibility note above.
detachAllDebuggersFromGlobal(fop, global, &e); detachAllDebuggersFromGlobal(fop, global, &e);
}
else if (global != e.front()) else if (global != e.front())
e.rekeyFront(global); e.rekeyFront(global);
} }
@ -1680,7 +1686,7 @@ Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global,
const GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
JS_ASSERT(!debuggers->empty()); JS_ASSERT(!debuggers->empty());
while (!debuggers->empty()) while (!debuggers->empty())
debuggers->back()->removeDebuggeeGlobal(fop, global, compartmentEnum, nullptr); debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, compartmentEnum, nullptr);
} }
/* static */ void /* static */ void
@ -2018,6 +2024,7 @@ Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp)
// Invalidate a zone at a time to avoid doing a zone-wide CellIter // Invalidate a zone at a time to avoid doing a zone-wide CellIter
// per compartment. // per compartment.
AutoDebugModeInvalidation invalidate(zone); AutoDebugModeInvalidation invalidate(zone);
for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
if (c == dbg->object->compartment() || c->options().invisibleToDebugger()) if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
continue; continue;
@ -2043,8 +2050,10 @@ Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]); GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
if (!global) if (!global)
return false; return false;
if (dbg->debuggees.has(global)) if (dbg->debuggees.has(global)) {
dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, nullptr, nullptr); if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, nullptr))
return false;
}
args.rval().setUndefined(); args.rval().setUndefined();
return true; return true;
} }
@ -2053,8 +2062,11 @@ bool
Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp) Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg); THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), e.front(), nullptr, &e); for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
if (!dbg->removeDebuggeeGlobal(cx, e.front(), nullptr, &e))
return false;
}
args.rval().setUndefined(); args.rval().setUndefined();
return true; return true;
@ -2098,15 +2110,14 @@ Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp)
/* Since there may be multiple contexts, use AllFramesIter. */ /* Since there may be multiple contexts, use AllFramesIter. */
for (AllFramesIter i(cx); !i.done(); ++i) { for (AllFramesIter i(cx); !i.done(); ++i) {
/* if (dbg->observesFrame(i)) {
* Debug-mode currently disables Ion compilation in the compartment of // Ensure that Ion frames are rematerialized. Only rematerialized
* the debuggee. // Ion frames may be used as AbstractFramePtrs.
*/ if (i.isIon() && !i.ensureHasRematerializedFrame())
if (i.isIon()) return false;
continue; AbstractFramePtr frame = i.abstractFramePtr();
if (dbg->observesFrame(i.abstractFramePtr())) {
ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED); ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED);
while (iter.isIon() || iter.abstractFramePtr() != i.abstractFramePtr()) while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
++iter; ++iter;
return dbg->getScriptFrame(cx, iter, args.rval()); return dbg->getScriptFrame(cx, iter, args.rval());
} }
@ -2248,12 +2259,6 @@ Debugger::addDebuggeeGlobal(JSContext *cx,
} }
} }
/* Refuse to enable debug mode for a compartment that has running scripts. */
if (!debuggeeCompartment->debugMode() && debuggeeCompartment->hasScriptsOnStack()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
return false;
}
/* /*
* Each debugger-debuggee relation must be stored in up to three places. * Each debugger-debuggee relation must be stored in up to three places.
* JSCompartment::addDebuggee enables debug mode if needed. * JSCompartment::addDebuggee enables debug mode if needed.
@ -2281,19 +2286,10 @@ Debugger::addDebuggeeGlobal(JSContext *cx,
} }
void void
Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, Debugger::cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
GlobalObjectSet::Enum *compartmentEnum, AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *debugEnum) GlobalObjectSet::Enum *compartmentEnum,
{ GlobalObjectSet::Enum *debugEnum)
AutoDebugModeInvalidation invalidate(global->compartment());
removeDebuggeeGlobal(fop, global, invalidate, compartmentEnum, debugEnum);
}
void
Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{ {
/* /*
* Each debuggee is in two HashSets: one for its compartment and one for * Each debuggee is in two HashSets: one for its compartment and one for
@ -2351,14 +2347,57 @@ Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
bp->destroy(fop); bp->destroy(fop);
} }
JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint()); JS_ASSERT_IF(debuggees.empty(), !firstBreakpoint());
}
bool
Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{
AutoDebugModeInvalidation invalidate(global->compartment());
return removeDebuggeeGlobal(cx, global, invalidate, compartmentEnum, debugEnum);
}
bool
Debugger::removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{
cleanupDebuggeeGlobalBeforeRemoval(cx->runtime()->defaultFreeOp(), global,
invalidate, compartmentEnum, debugEnum);
// The debuggee needs to be removed from the compartment last to save a root.
if (global->getDebuggers()->empty())
return global->compartment()->removeDebuggee(cx, global, invalidate, compartmentEnum);
return true;
}
void
Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{
AutoDebugModeInvalidation invalidate(global->compartment());
removeDebuggeeGlobalUnderGC(fop, global, invalidate, compartmentEnum, debugEnum);
}
void
Debugger::removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum)
{
cleanupDebuggeeGlobalBeforeRemoval(fop, global, invalidate, compartmentEnum, debugEnum);
/* /*
* The debuggee needs to be removed from the compartment last, as this can * The debuggee needs to be removed from the compartment last, as this can
* trigger GCs if the compartment's debug mode is being changed, and the * trigger GCs if the compartment's debug mode is being changed, and the
* global cannot be rooted on the stack without a cx. * global cannot be rooted on the stack without a cx.
*/ */
if (v->empty()) if (global->getDebuggers()->empty())
global->compartment()->removeDebuggee(fop, global, invalidate, compartmentEnum); global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum);
} }
/* /*
@ -3560,6 +3599,12 @@ Debugger::observesFrame(AbstractFramePtr frame) const
return observesScript(frame.script()); return observesScript(frame.script());
} }
bool
Debugger::observesFrame(const ScriptFrameIter &iter) const
{
return observesScript(iter.script());
}
bool bool
Debugger::observesScript(JSScript *script) const Debugger::observesScript(JSScript *script) const
{ {
@ -3570,12 +3615,10 @@ Debugger::observesScript(JSScript *script) const
} }
/* static */ bool /* static */ bool
Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to) Debugger::replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
ScriptFrameIter &iter)
{ {
ScriptFrameIter iter(cx); for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
JS_ASSERT(iter.abstractFramePtr() == to);
for (FrameRange r(from); !r.empty(); r.popFront()) {
RootedObject frameobj(cx, r.frontFrame()); RootedObject frameobj(cx, r.frontFrame());
Debugger *dbg = r.frontDebugger(); Debugger *dbg = r.frontDebugger();
JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj)); JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
@ -3600,6 +3643,31 @@ Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::Baseline
return true; return true;
} }
/* static */ bool
Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to)
{
ScriptFrameIter iter(cx);
JS_ASSERT(iter.abstractFramePtr() == to);
return replaceFrameGuts(cx, from, to, iter);
}
/* static */ bool
Debugger::handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to)
{
// When we return to a bailed-out Ion real frame, we must update all
// Debugger.Frames that refer to its inline frames. However, since we
// can't pop individual inline frames off the stack (we can only pop the
// real frame that contains them all, as a unit), we cannot assume that
// the frame we're dealing with is the top frame. Advance the iterator
// across any inlined frames younger than |to|, the baseline frame
// reconstructed during bailout from the Ion frame corresponding to
// |from|.
ScriptFrameIter iter(cx);
while (iter.abstractFramePtr() != to)
++iter;
return replaceFrameGuts(cx, from, to, iter);
}
static bool static bool
DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp) DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
{ {
@ -4033,6 +4101,43 @@ static const JSFunctionSpec DebuggerSource_methods[] = {
/*** Debugger.Frame ******************************************************************************/ /*** Debugger.Frame ******************************************************************************/
static void
UpdateFrameIterPc(FrameIter &iter)
{
if (iter.abstractFramePtr().isRematerializedFrame()) {
#ifdef DEBUG
// Rematerialized frames don't need their pc updated. The reason we
// need to update pc is because we might get the same Debugger.Frame
// object for multiple re-entries into debugger code from debuggee
// code. This reentrancy is not possible with rematerialized frames,
// because when returning to debuggee code, we would have bailed out
// to baseline.
//
// We walk the stack to assert that it doesn't need updating.
jit::RematerializedFrame *frame = iter.abstractFramePtr().asRematerializedFrame();
jit::IonJSFrameLayout *jsFrame = (jit::IonJSFrameLayout *)frame->top();
jit::JitActivation *activation = iter.activation()->asJit();
ActivationIterator activationIter(activation->cx()->runtime());
while (activationIter.activation() != activation)
++activationIter;
jit::JitFrameIterator jitIter(activationIter);
while (!jitIter.isIonJS() || jitIter.jsFrame() != jsFrame)
++jitIter;
jit::InlineFrameIterator ionInlineIter(activation->cx(), &jitIter);
while (ionInlineIter.frameNo() != frame->frameNo())
++ionInlineIter;
MOZ_ASSERT(ionInlineIter.pc() == iter.pc());
#endif
return;
}
iter.updatePcQuadratic();
}
static void static void
DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj) DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj)
{ {
@ -4175,6 +4280,27 @@ DebuggerFrame_getType(JSContext *cx, unsigned argc, Value *vp)
return true; return true;
} }
static bool
DebuggerFrame_getImplementation(JSContext *cx, unsigned argc, Value *vp)
{
THIS_FRAME(cx, argc, vp, "get implementation", args, thisobj, frame);
const char *s;
if (frame.isBaselineFrame())
s = "baseline";
else if (frame.isRematerializedFrame())
s = "ion";
else
s = "interpreter";
JSAtom *str = Atomize(cx, s, strlen(s));
if (!str)
return false;
args.rval().setString(str);
return true;
}
static bool static bool
DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp) DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
{ {
@ -4183,7 +4309,7 @@ DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
Rooted<Env*> env(cx); Rooted<Env*> env(cx);
{ {
AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain()); AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
iter.updatePcQuadratic(); UpdateFrameIterPc(iter);
env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc()); env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
if (!env) if (!env)
return false; return false;
@ -4243,10 +4369,11 @@ DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp)
Debugger *dbg = Debugger::fromChildJSObject(thisobj); Debugger *dbg = Debugger::fromChildJSObject(thisobj);
for (++iter; !iter.done(); ++iter) { for (++iter; !iter.done(); ++iter) {
if (iter.isIon()) if (dbg->observesFrame(iter)) {
continue; if (iter.isIon() && !iter.ensureHasRematerializedFrame())
if (dbg->observesFrame(iter.abstractFramePtr())) return false;
return dbg->getScriptFrame(cx, iter, args.rval()); return dbg->getScriptFrame(cx, iter, args.rval());
}
} }
args.rval().setNull(); args.rval().setNull();
return true; return true;
@ -4408,7 +4535,7 @@ DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp)
{ {
THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter); THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter);
JSScript *script = iter.script(); JSScript *script = iter.script();
iter.updatePcQuadratic(); UpdateFrameIterPc(iter);
jsbytecode *pc = iter.pc(); jsbytecode *pc = iter.pc();
size_t offset = script->pcToOffset(pc); size_t offset = script->pcToOffset(pc);
args.rval().setNumber(double(offset)); args.rval().setNumber(double(offset));
@ -4683,7 +4810,7 @@ DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter); THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1); REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1);
Debugger *dbg = Debugger::fromChildJSObject(thisobj); Debugger *dbg = Debugger::fromChildJSObject(thisobj);
iter.updatePcQuadratic(); UpdateFrameIterPc(iter);
return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval", return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval",
args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue, args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
args.get(1), args.rval(), dbg, js::NullPtr(), &iter); args.get(1), args.rval(), dbg, js::NullPtr(), &iter);
@ -4695,7 +4822,7 @@ DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp)
THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter); THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter);
REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2); REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2);
Debugger *dbg = Debugger::fromChildJSObject(thisobj); Debugger *dbg = Debugger::fromChildJSObject(thisobj);
iter.updatePcQuadratic(); UpdateFrameIterPc(iter);
return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings", return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings",
args[0], EvalHasExtraBindings, args[1], args.get(2), args[0], EvalHasExtraBindings, args[1], args.get(2),
args.rval(), dbg, js::NullPtr(), &iter); args.rval(), dbg, js::NullPtr(), &iter);
@ -4721,6 +4848,7 @@ static const JSPropertySpec DebuggerFrame_properties[] = {
JS_PSG("script", DebuggerFrame_getScript, 0), JS_PSG("script", DebuggerFrame_getScript, 0),
JS_PSG("this", DebuggerFrame_getThis, 0), JS_PSG("this", DebuggerFrame_getThis, 0),
JS_PSG("type", DebuggerFrame_getType, 0), JS_PSG("type", DebuggerFrame_getType, 0),
JS_PSG("implementation", DebuggerFrame_getImplementation, 0),
JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0), JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0), JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
JS_PS_END JS_PS_END

View File

@ -239,13 +239,24 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj); bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj, bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
AutoDebugModeInvalidation &invalidate); AutoDebugModeInvalidation &invalidate);
void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, void cleanupDebuggeeGlobalBeforeRemoval(FreeOp *fop, GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnu);
bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
GlobalObjectSet::Enum *compartmentEnum, GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum); GlobalObjectSet::Enum *debugEnum);
void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global, bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
AutoDebugModeInvalidation &invalidate, AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum, GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum); GlobalObjectSet::Enum *debugEnum);
void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum);
void removeDebuggeeGlobalUnderGC(FreeOp *fop, GlobalObject *global,
AutoDebugModeInvalidation &invalidate,
GlobalObjectSet::Enum *compartmentEnum,
GlobalObjectSet::Enum *debugEnum);
/* /*
* Cope with an error or exception in a debugger hook. * Cope with an error or exception in a debugger hook.
@ -382,6 +393,9 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link); static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link);
static bool replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
ScriptFrameIter &iter);
public: public:
Debugger(JSContext *cx, JSObject *dbg); Debugger(JSContext *cx, JSObject *dbg);
~Debugger(); ~Debugger();
@ -428,6 +442,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp); static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp);
static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp); static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp);
static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to); static bool handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to);
static bool handleIonBailout(JSContext *cx, jit::RematerializedFrame *from, jit::BaselineFrame *to);
/************************************* Functions for use by Debugger.cpp. */ /************************************* Functions for use by Debugger.cpp. */
@ -436,6 +451,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
inline bool observesNewGlobalObject() const; inline bool observesNewGlobalObject() const;
inline bool observesGlobal(GlobalObject *global) const; inline bool observesGlobal(GlobalObject *global) const;
bool observesFrame(AbstractFramePtr frame) const; bool observesFrame(AbstractFramePtr frame) const;
bool observesFrame(const ScriptFrameIter &iter) const;
bool observesScript(JSScript *script) const; bool observesScript(JSScript *script) const;
/* /*

View File

@ -762,7 +762,7 @@ GlobalObject::addDebugger(JSContext *cx, Handle<GlobalObject*> global, Debugger
if (debuggers->empty() && !global->compartment()->addDebuggee(cx, global)) if (debuggers->empty() && !global->compartment()->addDebuggee(cx, global))
return false; return false;
if (!debuggers->append(dbg)) { if (!debuggers->append(dbg)) {
global->compartment()->removeDebuggee(cx->runtime()->defaultFreeOp(), global); (void) global->compartment()->removeDebuggee(cx, global);
return false; return false;
} }
return true; return true;

View File

@ -1059,9 +1059,44 @@ FrameIter::isConstructing() const
MOZ_ASSUME_UNREACHABLE("Unexpected state"); MOZ_ASSUME_UNREACHABLE("Unexpected state");
} }
bool
FrameIter::ensureHasRematerializedFrame()
{
#ifdef JS_ION
MOZ_ASSERT(isIon());
return !!activation()->asJit()->getRematerializedFrame(activation()->cx(), data_.jitFrames_);
#else
return true;
#endif
}
bool
FrameIter::hasUsableAbstractFramePtr() const
{
switch (data_.state_) {
case DONE:
case ASMJS:
return false;
case JIT:
#ifdef JS_ION
if (data_.jitFrames_.isBaselineJS())
return true;
MOZ_ASSERT(data_.jitFrames_.isIonJS());
return !!activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
ionInlineFrames_.frameNo());
#endif
break;
case INTERP:
return true;
}
MOZ_ASSUME_UNREACHABLE("Unexpected state");
}
AbstractFramePtr AbstractFramePtr
FrameIter::abstractFramePtr() const FrameIter::abstractFramePtr() const
{ {
MOZ_ASSERT(hasUsableAbstractFramePtr());
switch (data_.state_) { switch (data_.state_) {
case DONE: case DONE:
case ASMJS: case ASMJS:
@ -1072,11 +1107,8 @@ FrameIter::abstractFramePtr() const
return data_.jitFrames_.baselineFrame(); return data_.jitFrames_.baselineFrame();
MOZ_ASSERT(data_.jitFrames_.isIonJS()); MOZ_ASSERT(data_.jitFrames_.isIonJS());
jit::RematerializedFrame *frame = return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(), ionInlineFrames_.frameNo());
ionInlineFrames_.frameNo());
MOZ_ASSERT(frame);
return frame;
#endif #endif
break; break;
} }

View File

@ -1666,9 +1666,19 @@ class FrameIter
size_t numFrameSlots() const; size_t numFrameSlots() const;
Value frameSlotValue(size_t index) const; Value frameSlotValue(size_t index) const;
// -------------------------------------------------------------------------- // Ensures that we have rematerialized the top frame and its associated
// The following functions can only be called when isInterp() or isBaseline() // inline frames. Can only be called when isIon().
// -------------------------------------------------------------------------- bool ensureHasRematerializedFrame();
// True when isInterp() or isBaseline(). True when isIon() if it
// has a rematerialized frame. False otherwise false otherwise.
bool hasUsableAbstractFramePtr() const;
// -----------------------------------------------------------
// The following functions can only be called when isInterp(),
// isBaseline(), or isIon(). Further, abstractFramePtr() can
// only be called when hasUsableAbstractFramePtr().
// -----------------------------------------------------------
AbstractFramePtr abstractFramePtr() const; AbstractFramePtr abstractFramePtr() const;
AbstractFramePtr copyDataAsAbstractFramePtr() const; AbstractFramePtr copyDataAsAbstractFramePtr() const;