Bug 934799 - Part 1: Lazify delazifying lazy scripts in debug mode. (r=jimb)

This commit is contained in:
Shu-yu Guo 2013-11-27 09:54:44 -08:00
parent e3e688d737
commit fa52de51e4
6 changed files with 110 additions and 59 deletions

View File

@ -141,8 +141,7 @@ CanLazilyParse(ExclusiveContext *cx, const ReadOnlyCompileOptions &options)
{
return options.canLazilyParse &&
options.compileAndGo &&
options.sourcePolicy == CompileOptions::SAVE_SOURCE &&
!cx->compartment()->debugMode();
options.sourcePolicy == CompileOptions::SAVE_SOURCE;
}
void

View File

@ -1774,7 +1774,9 @@ BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext *cx)
RootedFunction function(cx, script->function());
CallNewScriptHook(cx->asJSContext(), script, function);
if (!parent) {
// Lazy scripts are never top level (despite always being invoked with a
// nullptr parent), and so the hook should never be fired.
if (emitterMode != LazyFunction && !parent) {
GlobalObject *compileAndGoGlobal = nullptr;
if (script->compileAndGo)
compileAndGoGlobal = &script->global();

View File

@ -675,20 +675,20 @@ CreateLazyScriptsForCompartment(JSContext *cx)
{
AutoObjectVector lazyFunctions(cx);
// Find all root lazy functions in the compartment: those which have not been
// compiled and which have a source object, indicating that their parent has
// been compiled.
for (gc::CellIter i(cx->zone(), JSFunction::FinalizeKind); !i.done(); i.next()) {
JSObject *obj = i.get<JSObject>();
if (obj->compartment() == cx->compartment() && obj->is<JSFunction>()) {
JSFunction *fun = &obj->as<JSFunction>();
if (fun->isInterpretedLazy()) {
LazyScript *lazy = fun->lazyScriptOrNull();
if (lazy && lazy->sourceObject() && !lazy->maybeScript()) {
if (!lazyFunctions.append(fun))
return false;
}
}
// Find all live lazy scripts in the compartment, and via them all root
// lazy functions in the compartment: those which have not been compiled
// and which have a source object, indicating that their parent has been
// compiled.
for (gc::CellIter i(cx->zone(), gc::FINALIZE_LAZY_SCRIPT); !i.done(); i.next()) {
LazyScript *lazy = i.get<LazyScript>();
JSFunction *fun = lazy->function();
if (fun->compartment() == cx->compartment() &&
lazy->sourceObject() && !lazy->maybeScript())
{
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(lazy == fun->lazyScriptOrNull());
if (!lazyFunctions.append(fun))
return false;
}
}
@ -710,19 +710,16 @@ CreateLazyScriptsForCompartment(JSContext *cx)
return false;
}
// Repoint any clones of the original functions to their new script.
for (gc::CellIter i(cx->zone(), JSFunction::FinalizeKind); !i.done(); i.next()) {
JSObject *obj = i.get<JSObject>();
if (obj->compartment() == cx->compartment() && obj->is<JSFunction>()) {
JSFunction *fun = &obj->as<JSFunction>();
if (fun->isInterpretedLazy()) {
LazyScript *lazy = fun->lazyScriptOrNull();
if (lazy && lazy->maybeScript())
fun->existingScript();
}
}
}
return true;
}
bool
JSCompartment::ensureDelazifyScriptsForDebugMode(JSContext *cx)
{
MOZ_ASSERT(cx->compartment() == this);
if ((debugModeBits & DebugNeedDelazification) && !CreateLazyScriptsForCompartment(cx))
return false;
debugModeBits &= ~DebugNeedDelazification;
return true;
}
@ -730,7 +727,7 @@ bool
JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidation &invalidate)
{
bool enabledBefore = debugMode();
bool enabledAfter = (debugModeBits & ~unsigned(DebugFromC)) || b;
bool enabledAfter = (debugModeBits & DebugModeFromMask & ~DebugFromC) || b;
// Debug mode can be enabled only when no scripts from the target
// compartment are on the stack. It would even be incorrect to discard just
@ -749,11 +746,9 @@ JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidatio
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
return false;
}
if (enabledAfter && !CreateLazyScriptsForCompartment(cx))
return false;
}
debugModeBits = (debugModeBits & ~unsigned(DebugFromC)) | (b ? DebugFromC : 0);
debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0);
JS_ASSERT(debugMode() == enabledAfter);
if (enabledBefore != enabledAfter) {
updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate);
@ -803,8 +798,6 @@ JSCompartment::addDebuggee(JSContext *cx,
Rooted<GlobalObject*> global(cx, globalArg);
bool wasEnabled = debugMode();
if (!wasEnabled && !CreateLazyScriptsForCompartment(cx))
return false;
if (!debuggees.put(global)) {
js_ReportOutOfMemory(cx);
return false;

View File

@ -278,7 +278,13 @@ struct JSCompartment
js::WeakMapBase *gcWeakMapList;
private:
enum { DebugFromC = 1, DebugFromJS = 2 };
enum {
DebugFromC = 1 << 0,
DebugFromJS = 1 << 1,
DebugNeedDelazification = 1 << 2
};
static const unsigned DebugModeFromMask = DebugFromC | DebugFromJS;
unsigned debugModeBits; // see debugMode() below
@ -356,13 +362,34 @@ struct JSCompartment
* by Debugger objects. Therefore debugModeBits has the DebugFromC bit set
* if the C API wants debug mode and the DebugFromJS bit set if debuggees
* is non-empty.
*
* When toggling on, DebugNeedDelazification is set to signal that
* Debugger methods which depend on seeing all scripts (like findScripts)
* need to delazify the scripts in the compartment first.
*/
bool debugMode() const { return !!debugModeBits; }
bool debugMode() const {
return !!(debugModeBits & DebugModeFromMask);
}
/* True if any scripts from this compartment are on the JS stack. */
bool hasScriptsOnStack();
/*
* Schedule the compartment to be delazified. Called from
* LazyScript::Create.
*/
void scheduleDelazificationForDebugMode() {
debugModeBits |= DebugNeedDelazification;
}
/*
* If we scheduled delazification for turning on debug mode, delazify all
* scripts.
*/
bool ensureDelazifyScriptsForDebugMode(JSContext *cx);
private:
/* This is called only when debugMode() has just toggled. */
void updateForDebugMode(js::FreeOp *fop, js::AutoDebugModeInvalidation &invalidate);

View File

@ -3110,6 +3110,8 @@ LazyScript::Create(ExclusiveContext *cx, HandleFunction fun,
if (!res)
return nullptr;
cx->compartment()->scheduleDelazificationForDebugMode();
return new (res) LazyScript(fun, table, numFreeVariables, numInnerFunctions, version,
begin, end, lineno, column);
}

View File

@ -98,6 +98,25 @@ ReportMoreArgsNeeded(JSContext *cx, const char *name, unsigned required)
return false;
}
static inline bool
EnsureFunctionHasScript(JSContext *cx, JSFunction *fun)
{
if (fun->isInterpretedLazy()) {
AutoCompartment ac(cx, fun);
return !!fun->getOrCreateScript(cx);
}
return true;
}
static inline JSScript *
GetOrCreateFunctionScript(JSContext *cx, JSFunction *fun)
{
MOZ_ASSERT(fun->isInterpreted());
if (!EnsureFunctionHasScript(cx, fun))
return nullptr;
return fun->nonLazyScript();
}
#define REQUIRE_ARGC(name, n) \
JS_BEGIN_MACRO \
if (argc < (n)) \
@ -688,6 +707,9 @@ Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
if (vp.isObject()) {
RootedObject obj(cx, &vp.toObject());
if (obj->is<JSFunction>() && !EnsureFunctionHasScript(cx, &obj->as<JSFunction>()))
return false;
DependentAddPtr<ObjectWeakMap> p(cx, objects, obj);
if (p) {
vp.setObject(*p->value);
@ -2406,10 +2428,14 @@ class Debugger::ScriptQuery {
if (!prepareQuery())
return false;
JSCompartment *singletonComp = nullptr;
if (compartments.count() == 1)
singletonComp = compartments.all().front();
/* Search each compartment for debuggee scripts. */
vector = v;
oom = false;
IterateScripts(cx->runtime(), nullptr, this, considerScript);
IterateScripts(cx->runtime(), singletonComp, this, considerScript);
if (oom) {
js_ReportOutOfMemory(cx);
return false;
@ -2479,10 +2505,21 @@ class Debugger::ScriptQuery {
/* Indicates whether OOM has occurred while matching. */
bool oom;
bool addCompartment(JSCompartment *comp) {
{
// All scripts in the debuggee compartment must be visible, so
// delazify everything.
AutoCompartment ac(cx, comp);
if (!comp->ensureDelazifyScriptsForDebugMode(cx))
return false;
}
return compartments.put(comp);
}
/* Arrange for this ScriptQuery to match only scripts that run in |global|. */
bool matchSingleGlobal(GlobalObject *global) {
JS_ASSERT(compartments.count() == 0);
if (!compartments.put(global->compartment())) {
if (!addCompartment(global->compartment())) {
js_ReportOutOfMemory(cx);
return false;
}
@ -2497,7 +2534,7 @@ class Debugger::ScriptQuery {
JS_ASSERT(compartments.count() == 0);
/* Build our compartment set from the debugger's set of debuggee globals. */
for (GlobalObjectSet::Range r = debugger->debuggees.all(); !r.empty(); r.popFront()) {
if (!compartments.put(r.front()->compartment())) {
if (!addCompartment(r.front()->compartment())) {
js_ReportOutOfMemory(cx);
return false;
}
@ -2939,8 +2976,10 @@ DebuggerScript_getChildScripts(JSContext *cx, unsigned argc, Value *vp)
for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) {
obj = objects->vector[i];
if (obj->is<JSFunction>()) {
fun = static_cast<JSFunction *>(obj.get());
funScript = fun->nonLazyScript();
fun = &obj->as<JSFunction>();
funScript = GetOrCreateFunctionScript(cx, fun);
if (!funScript)
return false;
s = dbg->wrapScript(cx, funScript);
if (!s || !js_NewbornArrayPush(cx, result, ObjectValue(*s)))
return false;
@ -4685,14 +4724,9 @@ DebuggerObject_getParameterNames(JSContext *cx, unsigned argc, Value *vp)
result->ensureDenseInitializedLength(cx, 0, fun->nargs);
if (fun->isInterpreted()) {
RootedScript script(cx);
{
AutoCompartment ac(cx, fun);
script = fun->getOrCreateScript(cx);
if (!script)
return false;
}
RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
if (!script)
return false;
JS_ASSERT(fun->nargs == script->bindings.numArgs());
@ -4734,15 +4768,9 @@ DebuggerObject_getScript(JSContext *cx, unsigned argc, Value *vp)
return true;
}
RootedScript script(cx);
{
AutoCompartment ac(cx, obj);
script = fun->getOrCreateScript(cx);
if (!script)
return false;
}
RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
if (!script)
return false;
/* Only hand out debuggee scripts. */
if (!dbg->observesScript(script)) {