Backed out 7 changesets (bug 935228, bug 936143, bug 935470, bug 933882, bug 934799) for breaking ASAN browser-chrome tests on a CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Mon, 11 Nov 2013 16:53:59 -0800
changeset 169054 67f5d934127ce93057ad5c76d04af75337762434
parent 169053 98e2f5b76cc83ae49cc562b860a1d1ca5bd8e8c2
child 169055 8da3e678d396b1f6f62dc6b14dfac96f0f02bf25
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs935228, 936143, 935470, 933882, 934799
milestone28.0a1
backs outae6f2151610f88fe79d5500b5ea744828220190b
82495f0c5da2788ffa032e52a9a09b02ab860ace
77be849d81e7962c51663b1f5aa069c8d71611ae
555e5759fe5ff4ea3d420e6f9a54cc65d8f703d9
ce4011f334226ae21b698d04c30015102042ee73
e13e98eab890e20b45a35a5ca7b0c70549bc9799
fb230c191a88f464f6bcb5d0603becf803c4c8c1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 7 changesets (bug 935228, bug 936143, bug 935470, bug 933882, bug 934799) for breaking ASAN browser-chrome tests on a CLOSED TREE Backed out changeset ae6f2151610f (bug 934799) Backed out changeset 82495f0c5da2 (bug 934799) Backed out changeset 77be849d81e7 (bug 935228) Backed out changeset 555e5759fe5f (bug 935470) Backed out changeset ce4011f33422 (bug 933882) Backed out changeset e13e98eab890 (bug 936143) Backed out changeset fb230c191a88 (bug 936143)
js/public/GCAPI.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/jit-test/tests/debug/Script-getLineOffsets-07.js
js/src/jit-test/tests/debug/Script-startLine.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/Ion.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Interpreter.cpp
js/src/vm/OldDebugAPI.cpp
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -20,16 +20,17 @@ namespace JS {
     D(API)                                      \
     D(MAYBEGC)                                  \
     D(DESTROY_RUNTIME)                          \
     D(DESTROY_CONTEXT)                          \
     D(LAST_DITCH)                               \
     D(TOO_MUCH_MALLOC)                          \
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
+    D(DEBUG_MODE_GC)                            \
     D(TRANSPLANT)                               \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(FULL_STORE_BUFFER)                        \
                                                 \
     /* These are reserved for future use. */    \
     D(RESERVED0)                                \
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -137,18 +137,17 @@ MaybeCheckEvalFreeVariables(ExclusiveCon
 }
 
 static inline bool
 CanLazilyParse(ExclusiveContext *cx, const ReadOnlyCompileOptions &options)
 {
     return options.canLazilyParse &&
         options.compileAndGo &&
         options.sourcePolicy == CompileOptions::SAVE_SOURCE &&
-        !(cx->compartment()->debugMode() &&
-          cx->compartment()->runtimeFromAnyThread()->debugHooks.newScriptHook);
+        !cx->compartment()->debugMode();
 }
 
 void
 frontend::MaybeCallSourceHandler(JSContext *cx, const ReadOnlyCompileOptions &options,
                                  const jschar *chars, size_t length)
 {
     JSSourceHandler listener = cx->runtime()->debugHooks.sourceHandler;
     void *listenerData = cx->runtime()->debugHooks.sourceHandlerData;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1752,19 +1752,17 @@ BytecodeEmitter::tellDebuggerAboutCompil
 {
     // Note: when parsing off thread the resulting scripts need to be handed to
     // the debugger after rejoining to the main thread.
     if (!cx->isJSContext())
         return;
 
     RootedFunction function(cx, script->function());
     CallNewScriptHook(cx->asJSContext(), script, function);
-    // 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) {
+    if (!parent) {
         GlobalObject *compileAndGoGlobal = nullptr;
         if (script->compileAndGo)
             compileAndGoGlobal = &script->global();
         Debugger::onNewScript(cx->asJSContext(), script, compileAndGoGlobal);
     }
 }
 
 inline TokenStream *
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2116,19 +2116,16 @@ Parser<FullParseHandler>::functionArgsAn
                 return false;
             }
 
             outerpc->blockidGen = funpc.blockidGen;
 
             // Advance this parser over tokens processed by the syntax parser.
             parser->tokenStream.tell(&position);
             tokenStream.seek(position, parser->tokenStream);
-
-            // Update the end position of the parse node.
-            pn->pn_pos.end = tokenStream.currentToken().pos.end;
         }
 
         if (!addFreeVariablesFromLazyFunction(fun, pc))
             return false;
 
         pn->pn_blockid = outerpc->blockid();
         PropagateTransitiveParseFlags(funbox, outerpc->sc);
         return true;
deleted file mode 100644
--- a/js/src/jit-test/tests/debug/Script-getLineOffsets-07.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Lazy scripts should correctly report line offsets
-
-var g = newGlobal();
-var dbg = new Debugger();
-
-g.eval("// Header comment\n" +   // <- line 6 in this file
-       "\n" +
-       "\n" +
-       "function f(n) {\n" +     // <- line 9 in this file
-       "    var foo = '!';\n" +
-       "}");
-
-dbg.addDebuggee(g);
-var scripts = dbg.findScripts();
-var found = false;
-for (var i = 0; i < scripts.length; i++) {
-  found = found || scripts[i].startLine == 6;
-  // Nothing should have offsets for the deffun on line 9 if lazy scripts
-  // correctly update the position.
-  assertEq(scripts[i].getLineOffsets(9).length, 0);
-}
-assertEq(found, true);
--- a/js/src/jit-test/tests/debug/Script-startLine.js
+++ b/js/src/jit-test/tests/debug/Script-startLine.js
@@ -3,21 +3,19 @@ var dbg = Debugger(g);
 var start, count;
 dbg.onDebuggerStatement = function (frame) {
     assertEq(start, undefined);
     start = frame.script.startLine;
     count = frame.script.lineCount;
     assertEq(typeof frame.script.url, 'string');
 };
 
-function test(f, manualCount) {
+function test(f) {
     start = count = g.first = g.last = undefined;
     f();
-    if (manualCount)
-        g.last = g.first + manualCount - 1;
     assertEq(start, g.first);
     assertEq(count, g.last + 1 - g.first);
     print(start, count);
 }
 
 test(function () {
     g.eval("first = Error().lineNumber;\n" +
            "debugger;\n" +
@@ -38,24 +36,8 @@ test(function () {
            "f1();");
 });
 
 g.eval("function f2() {\n" +
        "    eval('first = Error().lineNumber\\n\\ndebugger;\\n\\nlast = Error().lineNumber;');\n" +
        "}\n");
 test(g.f2);
 test(g.f2);
-
-// Having a last = Error().lineNumber forces a setline srcnote, so test a
-// function that ends with newline srcnotes.
-g.eval("/* Any copyright is dedicated to the Public Domain.\n" +
-       " http://creativecommons.org/publicdomain/zero/1.0/ */\n" +
-       "\n" +
-       "function secondCall() { first = Error().lineNumber;\n" +
-       "    debugger;\n" +
-       "    // Comment\n" +
-       "    eval(\"42;\");\n" +
-       "    function foo() {}\n" +
-       "    if (true) {\n" +
-       "        foo();\n" +  // <- this is +6 and must be within the extent
-       "    }\n" +
-       "}");
-test(g.secondCall, 7);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -246,19 +246,16 @@ BaselineCompiler::compile()
 
         JS_ASSERT(added == script->nTypeSets);
 
         // The last entry in the last index found, and is used to avoid binary
         // searches for the sought entry when queries are in linear order.
         bytecodeMap[script->nTypeSets] = 0;
     }
 
-    if (script->compartment()->debugMode())
-        baselineScript->setDebugMode();
-
     return Method_Compiled;
 }
 
 bool
 BaselineCompiler::emitPrologue()
 {
     masm.push(BaselineFrameReg);
     masm.mov(BaselineStackReg, BaselineFrameReg);
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -741,20 +741,16 @@ BaselineScript::pcForReturnAddress(JSScr
     return pcForReturnOffset(script, uint32_t(nativeAddress - method_->raw()));
 }
 
 void
 BaselineScript::toggleDebugTraps(JSScript *script, jsbytecode *pc)
 {
     JS_ASSERT(script->baselineScript() == this);
 
-    // Only scripts compiled for debug mode have toggled calls.
-    if (!debugMode())
-        return;
-
     SrcNoteLineScanner scanner(script->notes(), script->lineno);
 
     JSRuntime *rt = script->runtimeFromMainThread();
     IonContext ictx(rt, script->compartment(), nullptr);
     AutoFlushCache afc("DebugTraps", rt->jitRuntime());
 
     for (uint32_t i = 0; i < numPCMappingIndexEntries(); i++) {
         PCMappingIndexEntry &entry = pcMappingIndexEntry(i);
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -127,21 +127,17 @@ struct BaselineScript
         NEEDS_ARGS_OBJ = 1 << 0,
 
         // Flag set when discarding JIT code, to indicate this script is
         // on the stack and should not be discarded.
         ACTIVE = 1 << 1,
 
         // Flag set when the script contains any writes to its on-stack
         // (rather than call object stored) arguments.
-        MODIFIES_ARGUMENTS = 1 << 2,
-
-        // Flag set when compiled for use for debug mode. Handles various
-        // Debugger hooks and compiles toggled calls for traps.
-        DEBUG_MODE = 1 << 3
+        MODIFIES_ARGUMENTS = 1 << 2
     };
 
   private:
     uint32_t flags_;
 
   private:
     void trace(JSTracer *trc);
 
@@ -200,23 +196,16 @@ struct BaselineScript
 
     void setModifiesArguments() {
         flags_ |= MODIFIES_ARGUMENTS;
     }
     bool modifiesArguments() {
         return flags_ & MODIFIES_ARGUMENTS;
     }
 
-    void setDebugMode() {
-        flags_ |= DEBUG_MODE;
-    }
-    bool debugMode() const {
-        return flags_ & DEBUG_MODE;
-    }
-
     uint32_t prologueOffset() const {
         return prologueOffset_;
     }
     uint8_t *prologueEntryAddr() const {
         return method_->raw() + prologueOffset_;
     }
 
     ICEntry *icEntryList() {
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2349,30 +2349,25 @@ InvalidateActivation(FreeOp *fop, uint8_
         IonSpew(IonSpew_Invalidate, "   ! Invalidate ionScript %p (ref %u) -> patching osipoint %p",
                 ionScript, ionScript->refcount(), (void *) osiPatchPoint.raw());
         Assembler::patchWrite_NearCall(osiPatchPoint, invalidateEpilogue);
     }
 
     IonSpew(IonSpew_Invalidate, "END invalidating activation");
 }
 
-static inline void
-StopOffThreadCompilation(JSCompartment *comp)
+void
+jit::InvalidateAll(FreeOp *fop, Zone *zone)
 {
-    if (comp->jitCompartment()) {
+    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
+        if (!comp->jitCompartment())
+            continue;
         CancelOffThreadIonCompile(comp, nullptr);
         FinishAllOffThreadCompilations(comp->jitCompartment());
     }
-}
-
-void
-jit::InvalidateAll(FreeOp *fop, Zone *zone)
-{
-    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
-        StopOffThreadCompilation(comp);
 
     for (JitActivationIterator iter(fop->runtime()); !iter.done(); ++iter) {
         if (iter.activation()->compartment()->zone() == zone) {
             IonContext ictx(fop->runtime());
             AutoFlushCache afc("InvalidateAll", fop->runtime()->jitRuntime());
             IonSpew(IonSpew_Invalidate, "Invalidating all frames for GC");
             InvalidateActivation(fop, iter.jitTop(), true);
         }
@@ -2709,61 +2704,8 @@ jit::TraceIonScripts(JSTracer* trc, JSSc
         jit::IonScript::Trace(trc, script->ionScript());
 
     if (script->hasParallelIonScript())
         jit::IonScript::Trace(trc, script->parallelIonScript());
 
     if (script->hasBaselineScript())
         jit::BaselineScript::Trace(trc, script->baselineScript());
 }
-
-AutoDebugModeInvalidation::~AutoDebugModeInvalidation()
-{
-    MOZ_ASSERT(!!comp_ != !!zone_);
-
-    if (needInvalidation_ == NoNeed)
-        return;
-
-    // Invalidate the stack if any compartments toggled from on->off, because
-    // we allow scripts to be on stack when turning off debug mode.
-    bool invalidateStack = needInvalidation_ == ToggledOff;
-    Zone *zone = zone_ ? zone_ : comp_->zone();
-    JSRuntime *rt = zone->runtimeFromMainThread();
-    FreeOp *fop = rt->defaultFreeOp();
-
-    if (comp_) {
-        StopOffThreadCompilation(comp_);
-    } else {
-        for (CompartmentsInZoneIter comp(zone_); !comp.done(); comp.next())
-            StopOffThreadCompilation(comp);
-    }
-
-    if (invalidateStack) {
-        jit::MarkActiveBaselineScripts(zone);
-
-        for (JitActivationIterator iter(rt); !iter.done(); ++iter) {
-            JSCompartment *comp = iter.activation()->compartment();
-            if ((comp_ && comp_ == comp) ||
-                (zone_ && zone_ == comp->zone() && comp->principals))
-            {
-                IonContext ictx(rt);
-                AutoFlushCache afc("AutoDebugModeInvalidation", rt->jitRuntime());
-                IonSpew(IonSpew_Invalidate, "Invalidating frames for debug mode toggle");
-                InvalidateActivation(fop, iter.jitTop(), true);
-            }
-        }
-    }
-
-    for (gc::CellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
-        JSScript *script = i.get<JSScript>();
-        if ((comp_ && script->compartment() == comp_) ||
-            (zone_ && script->compartment()->principals))
-        {
-            FinishInvalidation(fop, script);
-            FinishDiscardBaselineScript(fop, script);
-            script->clearAnalysis();
-            script->resetUseCount();
-        } else {
-            if (script->hasBaselineScript())
-                script->baselineScript()->resetActive();
-        }
-    }
-}
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -668,30 +668,30 @@ AddInnerLazyFunctionsFromScript(JSScript
     return true;
 }
 
 static bool
 CreateLazyScriptsForCompartment(JSContext *cx)
 {
     AutoObjectVector lazyFunctions(cx);
 
-    // 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;
+    // 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;
+                }
+            }
         }
     }
 
     // Create scripts for each lazy function, updating the list of functions to
     // process with any newly exposed inner functions in created scripts.
     // A function cannot be delazified until its outer script exists.
     for (size_t i = 0; i < lazyFunctions.length(); i++) {
         JSFunction *fun = &lazyFunctions[i]->as<JSFunction>();
@@ -703,34 +703,37 @@ CreateLazyScriptsForCompartment(JSContex
 
         JSScript *script = fun->getOrCreateScript(cx);
         if (!script)
             return false;
         if (!AddInnerLazyFunctionsFromScript(script, lazyFunctions))
             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;
-}
-
-bool
-JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidation &invalidate)
+JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeGC &dmgc)
 {
     bool enabledBefore = debugMode();
-    bool enabledAfter = (debugModeBits & DebugModeFromMask & ~DebugFromC) || b;
+    bool enabledAfter = (debugModeBits & ~unsigned(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
     // 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
     // that case the debug-mode code for those scripts remains, so subsequently
@@ -739,105 +742,117 @@ JSCompartment::setDebugModeFromC(JSConte
     //
     bool onStack = false;
     if (enabledBefore != enabledAfter) {
         onStack = hasScriptsOnStack();
         if (b && onStack) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
+        if (enabledAfter && !CreateLazyScriptsForCompartment(cx))
+            return false;
     }
 
-    debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0);
+    debugModeBits = (debugModeBits & ~unsigned(DebugFromC)) | (b ? DebugFromC : 0);
     JS_ASSERT(debugMode() == enabledAfter);
     if (enabledBefore != enabledAfter) {
-        updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate);
+        updateForDebugMode(cx->runtime()->defaultFreeOp(), dmgc);
         if (!enabledAfter)
             DebugScopes::onCompartmentLeaveDebugMode(this);
     }
     return true;
 }
 
 void
-JSCompartment::updateForDebugMode(FreeOp *fop, AutoDebugModeInvalidation &invalidate)
+JSCompartment::updateForDebugMode(FreeOp *fop, AutoDebugModeGC &dmgc)
 {
     JSRuntime *rt = runtimeFromMainThread();
 
     for (ContextIter acx(rt); !acx.done(); acx.next()) {
         if (acx->compartment() == this)
             acx->updateJITEnabled();
     }
 
 #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.
+    // When we change a compartment's debug mode, whether we're turning it
+    // on or off, we must always throw away all analyses: debug mode
+    // affects various aspects of the analysis, which then get baked into
+    // SSA results, which affects code generation in complicated ways. We
+    // must also throw away all JIT code, as its soundness depends on the
+    // analyses.
     //
-    // The AutoDebugModeInvalidation argument makes sure we can't forget to
-    // invalidate, but it is also important not to run any scripts in this
-    // compartment until the invalidate is destroyed.  That is the caller's
-    // responsibility.
-    invalidate.scheduleInvalidation(debugMode());
+    // It suffices to do a garbage collection cycle or to finish the
+    // ongoing GC cycle. The necessary cleanup happens in
+    // JSCompartment::sweep.
+    //
+    // dmgc makes sure we can't forget to GC, but it is also important not
+    // to run any scripts in this compartment until the dmgc is destroyed.
+    // That is the caller's responsibility.
+    if (!rt->isHeapBusy())
+        dmgc.scheduleGC(zone());
 #endif
 }
 
 bool
 JSCompartment::addDebuggee(JSContext *cx, js::GlobalObject *global)
 {
-    AutoDebugModeInvalidation invalidate(this);
-    return addDebuggee(cx, global, invalidate);
+    AutoDebugModeGC dmgc(cx->runtime());
+    return addDebuggee(cx, global, dmgc);
 }
 
 bool
 JSCompartment::addDebuggee(JSContext *cx,
                            GlobalObject *globalArg,
-                           AutoDebugModeInvalidation &invalidate)
+                           AutoDebugModeGC &dmgc)
 {
     Rooted<GlobalObject*> global(cx, globalArg);
 
     bool wasEnabled = debugMode();
+    if (!wasEnabled && !CreateLazyScriptsForCompartment(cx))
+        return false;
     if (!debuggees.put(global)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     debugModeBits |= DebugFromJS;
-    if (!wasEnabled)
-        updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate);
+    if (!wasEnabled) {
+        updateForDebugMode(cx->runtime()->defaultFreeOp(), dmgc);
+    }
     return true;
 }
 
 void
 JSCompartment::removeDebuggee(FreeOp *fop,
                               js::GlobalObject *global,
                               js::GlobalObjectSet::Enum *debuggeesEnum)
 {
-    AutoDebugModeInvalidation invalidate(this);
-    return removeDebuggee(fop, global, invalidate, debuggeesEnum);
+    AutoDebugModeGC dmgc(fop->runtime());
+    return removeDebuggee(fop, global, dmgc, debuggeesEnum);
 }
 
 void
 JSCompartment::removeDebuggee(FreeOp *fop,
                               js::GlobalObject *global,
-                              AutoDebugModeInvalidation &invalidate,
+                              AutoDebugModeGC &dmgc,
                               js::GlobalObjectSet::Enum *debuggeesEnum)
 {
     bool wasEnabled = debugMode();
     JS_ASSERT(debuggees.has(global));
     if (debuggeesEnum)
         debuggeesEnum->removeFront();
     else
         debuggees.remove(global);
 
     if (debuggees.empty()) {
         debugModeBits &= ~DebugFromJS;
         if (wasEnabled && !debugMode()) {
             DebugScopes::onCompartmentLeaveDebugMode(this);
-            updateForDebugMode(fop, invalidate);
+            updateForDebugMode(fop, dmgc);
         }
     }
 }
 
 void
 JSCompartment::clearBreakpointsIn(FreeOp *fop, js::Debugger *dbg, JSObject *handler)
 {
     for (gc::CellIter i(zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -105,17 +105,17 @@ typedef HashMap<CrossCompartmentKey, Rea
 
 } /* namespace js */
 
 namespace JS {
 struct TypeInferenceSizes;
 }
 
 namespace js {
-class AutoDebugModeInvalidation;
+class AutoDebugModeGC;
 class ArrayBufferObject;
 class DebugScopes;
 class WeakMapBase;
 }
 
 struct JSCompartment
 {
     JS::CompartmentOptions       options_;
@@ -273,23 +273,17 @@ struct JSCompartment
 
     /* Linked list of live array buffers with >1 view. */
     js::ArrayBufferObject        *gcLiveArrayBuffers;
 
     /* Linked list of live weakmaps in this compartment. */
     js::WeakMapBase              *gcWeakMapList;
 
   private:
-    enum {
-        DebugFromC = 1 << 0,
-        DebugFromJS = 1 << 1,
-        DebugNeedDelazification = 1 << 2
-    };
-
-    static const unsigned DebugModeFromMask = DebugFromC | DebugFromJS;
+    enum { DebugFromC = 1, DebugFromJS = 2 };
 
     unsigned                     debugModeBits;  // see debugMode() below
 
   public:
     JSCompartment(JS::Zone *zone, const JS::CompartmentOptions &options);
     ~JSCompartment();
 
     bool init(JSContext *cx);
@@ -357,59 +351,37 @@ struct JSCompartment
 
   public:
     /*
      * There are dueling APIs for debug mode. It can be enabled or disabled via
      * JS_SetDebugModeForCompartment. It is automatically enabled and disabled
      * 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 & DebugModeFromMask);
-    }
+    bool debugMode() const { return !!debugModeBits; }
 
     /* 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);
+    void updateForDebugMode(js::FreeOp *fop, js::AutoDebugModeGC &dmgc);
 
   public:
     js::GlobalObjectSet &getDebuggees() { return debuggees; }
     bool addDebuggee(JSContext *cx, js::GlobalObject *global);
     bool addDebuggee(JSContext *cx, js::GlobalObject *global,
-                     js::AutoDebugModeInvalidation &invalidate);
+                     js::AutoDebugModeGC &dmgc);
     void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global,
                         js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
     void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global,
-                        js::AutoDebugModeInvalidation &invalidate,
+                        js::AutoDebugModeGC &dmgc,
                         js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
-    bool setDebugModeFromC(JSContext *cx, bool b,
-                           js::AutoDebugModeInvalidation &invalidate);
+    bool setDebugModeFromC(JSContext *cx, bool b, js::AutoDebugModeGC &dmgc);
 
     void clearBreakpointsIn(js::FreeOp *fop, js::Debugger *dbg, JSObject *handler);
     void clearTraps(js::FreeOp *fop);
 
   private:
     void sweepBreakpoints(js::FreeOp *fop);
 
   public:
@@ -445,66 +417,39 @@ struct JSCompartment
 
 inline bool
 JSRuntime::isAtomsZone(JS::Zone *zone)
 {
     return zone == atomsCompartment_->zone();
 }
 
 // For use when changing the debug mode flag on one or more compartments.
-// Invalidate and discard JIT code since debug mode breaks JIT assumptions.
-//
-// AutoDebugModeInvalidation has two modes: compartment or zone
-// invalidation. While it is correct to always use compartment invalidation,
-// if you know ahead of time you need to invalidate a whole zone, it is faster
-// to invalidate the zone.
-//
-// Compartment invalidation only invalidates scripts belonging to that
-// compartment.
+// Do not run scripts in any compartment that is scheduled for GC using this
+// object. See comment in updateForDebugMode.
 //
-// Zone invalidation invalidates all scripts belonging to non-special
-// (i.e. those with principals) compartments of the zone.
-//
-// FIXME: Remove entirely once bug 716647 lands.
-//
-class js::AutoDebugModeInvalidation
+class js::AutoDebugModeGC
 {
-    JSCompartment *comp_;
-    JS::Zone *zone_;
+    JSRuntime *rt;
+    bool needGC;
+  public:
+    explicit AutoDebugModeGC(JSRuntime *rt) : rt(rt), needGC(false) {}
 
-    enum {
-        NoNeed = 0,
-        ToggledOn = 1,
-        ToggledOff = 2
-    } needInvalidation_;
-
-  public:
-    explicit AutoDebugModeInvalidation(JSCompartment *comp)
-      : comp_(comp), zone_(nullptr), needInvalidation_(NoNeed)
-    { }
-
-    explicit AutoDebugModeInvalidation(JS::Zone *zone)
-      : comp_(nullptr), zone_(zone), needInvalidation_(NoNeed)
-    { }
-
-    ~AutoDebugModeInvalidation();
-
-    bool isFor(JSCompartment *comp) {
-        if (comp_)
-            return comp == comp_;
-        return comp->zone() == zone_;
+    ~AutoDebugModeGC() {
+        // Under some circumstances (say, in the midst of an animation),
+        // the garbage collector may try to retain JIT code and analyses.
+        // The DEBUG_MODE_GC reason forces the collector to always throw
+        // everything away, as required for debug mode transitions.
+        if (needGC)
+            GC(rt, GC_NORMAL, JS::gcreason::DEBUG_MODE_GC);
     }
 
-    void scheduleInvalidation(bool debugMode) {
-        // If we are scheduling invalidation for multiple compartments, they
-        // must all agree on the toggle. This is so we can decide if we need
-        // to invalidate on-stack scripts.
-        MOZ_ASSERT_IF(needInvalidation_ != NoNeed,
-                      needInvalidation_ == debugMode ? ToggledOn : ToggledOff);
-        needInvalidation_ = debugMode ? ToggledOn : ToggledOff;
+    void scheduleGC(Zone *zone) {
+        JS_ASSERT(!rt->isHeapBusy());
+        PrepareZoneForGC(zone);
+        needGC = true;
     }
 };
 
 namespace js {
 
 inline bool
 ExclusiveContext::typeInferenceEnabled() const
 {
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -4615,18 +4615,23 @@ IsDeterministicGCReason(JS::gcreason::Re
 #endif
 
 static bool
 ShouldCleanUpEverything(JSRuntime *rt, JS::gcreason::Reason reason, JSGCInvocationKind gckind)
 {
     // During shutdown, we must clean everything up, for the sake of leak
     // detection. When a runtime has no contexts, or we're doing a GC before a
     // shutdown CC, those are strong indications that we're shutting down.
+    //
+    // DEBUG_MODE_GC indicates we're discarding code because the debug mode
+    // has changed; debug mode affects the results of bytecode analysis, so
+    // we need to clear everything away.
     return reason == JS::gcreason::DESTROY_RUNTIME ||
            reason == JS::gcreason::SHUTDOWN_CC ||
+           reason == JS::gcreason::DEBUG_MODE_GC ||
            gckind == GC_SHRINK;
 }
 
 namespace {
 
 #ifdef JSGC_GENERATIONAL
 class AutoDisableStoreBuffer
 {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2199,29 +2199,39 @@ js_LineNumberToPC(JSScript *script, unsi
 out:
     return script->code + offset;
 }
 
 JS_FRIEND_API(unsigned)
 js_GetScriptLineExtent(JSScript *script)
 {
     unsigned lineno = script->lineno;
-    unsigned maxLineNo = lineno;
+    unsigned maxLineNo = 0;
+    bool counting = true;
     for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) {
         SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
-        if (type == SRC_SETLINE)
+        if (type == SRC_SETLINE) {
+            if (maxLineNo < lineno)
+                maxLineNo = lineno;
             lineno = (unsigned) js_GetSrcNoteOffset(sn, 0);
-        else if (type == SRC_NEWLINE)
-            lineno++;
-
-        if (maxLineNo < lineno)
-            maxLineNo = lineno;
+            counting = true;
+            if (maxLineNo < lineno)
+                maxLineNo = lineno;
+            else
+                counting = false;
+        } else if (type == SRC_NEWLINE) {
+            if (counting)
+                lineno++;
+        }
     }
 
-    return 1 + maxLineNo - script->lineno;
+    if (maxLineNo > lineno)
+        lineno = maxLineNo;
+
+    return 1 + lineno - script->lineno;
 }
 
 void
 js::CurrentScriptFileLineOrigin(JSContext *cx, const char **file, unsigned *linenop,
                                 JSPrincipals **origin, LineOption opt)
 {
     if (opt == CALLED_FROM_JSOP_EVAL) {
         jsbytecode *pc = nullptr;
@@ -2989,18 +2999,16 @@ LazyScript::Create(ExclusiveContext *cx,
         if (!table)
             return nullptr;
     }
 
     LazyScript *res = js_NewGCLazyScript(cx);
     if (!res)
         return nullptr;
 
-    cx->compartment()->scheduleDelazificationForDebugMode();
-
     return new (res) LazyScript(fun, table, numFreeVariables, numInnerFunctions, version,
                                 begin, end, lineno, column);
 }
 
 uint32_t
 LazyScript::staticLevel(JSContext *cx) const
 {
     for (StaticScopeIter<NoGC> ssi(enclosingScope()); !ssi.done(); ssi++) {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -92,35 +92,16 @@ ReportMoreArgsNeeded(JSContext *cx, cons
     char s[2];
     s[0] = '0' + (required - 1);
     s[1] = '\0';
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                          name, s, required == 2 ? "" : "s");
     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))                                                       \
             return ReportMoreArgsNeeded(cx, name, n);                         \
     JS_END_MACRO
 
 static bool
 ReportObjectRequired(JSContext *cx)
@@ -700,19 +681,16 @@ Debugger::wrapEnvironment(JSContext *cx,
 bool
 Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
 {
     assertSameCompartment(cx, object.get());
 
     if (vp.isObject()) {
         RootedObject obj(cx, &vp.toObject());
 
-        if (obj->is<JSFunction>() && !EnsureFunctionHasScript(cx, &obj->as<JSFunction>()))
-            return false;
-
         ObjectWeakMap::AddPtr p = objects.lookupForAdd(obj);
         if (p) {
             vp.setObject(*p->value);
         } else {
             /* Create a new Debugger.Object for obj. */
             JSObject *proto = &object->getReservedSlot(JSSLOT_DEBUG_OBJECT_PROTO).toObject();
             JSObject *dobj =
                 NewObjectWithGivenProto(cx, &DebuggerObject_class, proto, nullptr, TenuredObject);
@@ -1140,18 +1118,16 @@ Debugger::slowPathOnNewScript(JSContext 
             dbg->fireNewScript(cx, script);
         }
     }
 }
 
 JSTrapStatus
 Debugger::onTrap(JSContext *cx, MutableHandleValue vp)
 {
-    MOZ_ASSERT(cx->compartment()->debugMode());
-
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
     Rooted<GlobalObject*> scriptGlobal(cx, &script->global());
     jsbytecode *pc = iter.pc();
     BreakpointSite *site = script->getBreakpointSite(pc);
     JSOp op = JSOp(*pc);
 
     /* Build list of breakpoint handlers. */
@@ -1968,30 +1944,26 @@ Debugger::addDebuggee(JSContext *cx, uns
     args.rval().set(v);
     return true;
 }
 
 bool
 Debugger::addAllGlobalsAsDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "addAllGlobalsAsDebuggees", args, dbg);
-    for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
-        // Invalidate a zone at a time to avoid doing a zone-wide CellIter
-        // per compartment.
-        AutoDebugModeInvalidation invalidate(zone);
-        for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
-            if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
-                continue;
-            c->zone()->scheduledForDestruction = false;
-            GlobalObject *global = c->maybeGlobal();
-            if (global) {
-                Rooted<GlobalObject*> rg(cx, global);
-                if (!dbg->addDebuggeeGlobal(cx, rg, invalidate))
-                    return false;
-            }
+    AutoDebugModeGC dmgc(cx->runtime());
+    for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+        if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
+            continue;
+        c->zone()->scheduledForDestruction = false;
+        GlobalObject *global = c->maybeGlobal();
+        if (global) {
+            Rooted<GlobalObject*> rg(cx, global);
+            if (!dbg->addDebuggeeGlobal(cx, rg, dmgc))
+                return false;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 bool
@@ -2007,19 +1979,19 @@ Debugger::removeDebuggee(JSContext *cx, 
     args.rval().setUndefined();
     return true;
 }
 
 bool
 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "removeAllDebuggees", args, dbg);
+    AutoDebugModeGC dmgc(cx->runtime());
     for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
-        dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), e.front(), nullptr, &e);
-
+        dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), e.front(), dmgc, nullptr, &e);
     args.rval().setUndefined();
     return true;
 }
 
 bool
 Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
 {
     REQUIRE_ARGC("Debugger.hasDebuggee", 1);
@@ -2140,24 +2112,24 @@ Debugger::construct(JSContext *cx, unsig
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 Debugger::addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> global)
 {
-    AutoDebugModeInvalidation invalidate(global->compartment());
-    return addDebuggeeGlobal(cx, global, invalidate);
+    AutoDebugModeGC dmgc(cx->runtime());
+    return addDebuggeeGlobal(cx, global, dmgc);
 }
 
 bool
 Debugger::addDebuggeeGlobal(JSContext *cx,
                             Handle<GlobalObject*> global,
-                            AutoDebugModeInvalidation &invalidate)
+                            AutoDebugModeGC &dmgc)
 {
     if (debuggees.has(global))
         return true;
 
     // Callers should generally be unable to get a reference to a debugger-
     // invisible global in order to pass it to addDebuggee. But this is possible
     // with certain testing aides we expose in the shell, so just make addDebuggee
     // throw in that case.
@@ -2213,40 +2185,40 @@ Debugger::addDebuggeeGlobal(JSContext *c
     if (!v || !v->append(this)) {
         js_ReportOutOfMemory(cx);
     } else {
         if (!debuggees.put(global)) {
             js_ReportOutOfMemory(cx);
         } else {
             if (global->getDebuggers()->length() > 1)
                 return true;
-            if (debuggeeCompartment->addDebuggee(cx, global, invalidate))
+            if (debuggeeCompartment->addDebuggee(cx, global, dmgc))
                 return true;
 
             /* Maintain consistency on error. */
             debuggees.remove(global);
         }
         JS_ASSERT(v->back() == this);
         v->popBack();
     }
     return false;
 }
 
 void
 Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
                                GlobalObjectSet::Enum *compartmentEnum,
                                GlobalObjectSet::Enum *debugEnum)
 {
-    AutoDebugModeInvalidation invalidate(global->compartment());
-    return removeDebuggeeGlobal(fop, global, invalidate, compartmentEnum, debugEnum);
+    AutoDebugModeGC dmgc(fop->runtime());
+    return removeDebuggeeGlobal(fop, global, dmgc, compartmentEnum, debugEnum);
 }
 
 void
 Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
-                               AutoDebugModeInvalidation &invalidate,
+                               AutoDebugModeGC &dmgc,
                                GlobalObjectSet::Enum *compartmentEnum,
                                GlobalObjectSet::Enum *debugEnum)
 {
     /*
      * Each debuggee is in two HashSets: one for its compartment and one for
      * its debugger (this). The caller might be enumerating either set; if so,
      * use HashSet::Enum::removeFront rather than HashSet::remove below, to
      * avoid invalidating the live enumerator.
@@ -2292,17 +2264,17 @@ Debugger::removeDebuggeeGlobal(FreeOp *f
         debuggees.remove(global);
 
     /*
      * 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
      * global cannot be rooted on the stack without a cx.
      */
     if (v->empty())
-        global->compartment()->removeDebuggee(fop, global, invalidate, compartmentEnum);
+        global->compartment()->removeDebuggee(fop, global, dmgc, compartmentEnum);
 }
 
 /*
  * A class for parsing 'findScripts' query arguments and searching for
  * scripts that match the criteria they represent.
  */
 class Debugger::ScriptQuery {
   public:
@@ -2420,24 +2392,20 @@ class Debugger::ScriptQuery {
     /*
      * Search all relevant compartments and the stack for scripts matching
      * this query, and append the matching scripts to |vector|.
      */
     bool findScripts(AutoScriptVector *v) {
         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(), singletonComp, this, considerScript);
+        IterateScripts(cx->runtime(), nullptr, this, considerScript);
         if (oom) {
             js_ReportOutOfMemory(cx);
             return false;
         }
 
         /*
          * For most queries, we just accumulate results in 'vector' as we find
          * them. But if this is an 'innermost' query, then we've accumulated the
@@ -2497,46 +2465,35 @@ class Debugger::ScriptQuery {
     CompartmentToScriptMap innermostForCompartment;
 
     /* The vector to which to append the scripts found. */
     AutoScriptVector *vector;
 
     /* 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 (!addCompartment(global->compartment())) {
+        if (!compartments.put(global->compartment())) {
             js_ReportOutOfMemory(cx);
             return false;
         }
         return true;
     }
 
     /*
      * Arrange for this ScriptQuery to match all scripts running in debuggee
      * globals.
      */
     bool matchAllDebuggeeGlobals() {
         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 (!addCompartment(r.front()->compartment())) {
+            if (!compartments.put(r.front()->compartment())) {
                 js_ReportOutOfMemory(cx);
                 return false;
             }
         }
         return true;
     }
 
     /*
@@ -2969,20 +2926,18 @@ DebuggerScript_getChildScripts(JSContext
          */
         ObjectArray *objects = script->objects();
         RootedFunction fun(cx);
         RootedScript funScript(cx);
         RootedObject obj(cx), s(cx);
         for (uint32_t i = script->innerObjectsStart(); i < objects->length; i++) {
             obj = objects->vector[i];
             if (obj->is<JSFunction>()) {
-                fun = &obj->as<JSFunction>();
-                funScript = GetOrCreateFunctionScript(cx, fun);
-                if (!funScript)
-                    return false;
+                fun = static_cast<JSFunction *>(obj.get());
+                funScript = fun->nonLazyScript();
                 s = dbg->wrapScript(cx, funScript);
                 if (!s || !js_NewbornArrayPush(cx, result, ObjectValue(*s)))
                     return false;
             }
         }
     }
     args.rval().setObject(*result);
     return true;
@@ -4693,19 +4648,24 @@ DebuggerObject_getParameterNames(JSConte
     }
 
     RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, fun->nargs);
 
     if (fun->isInterpreted()) {
-        RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
-        if (!script)
-            return false;
+        RootedScript script(cx);
+
+        {
+            AutoCompartment ac(cx, fun);
+            script = fun->getOrCreateScript(cx);
+            if (!script)
+                return false;
+        }
 
         JS_ASSERT(fun->nargs == script->bindings.numArgs());
 
         if (fun->nargs > 0) {
             BindingVector bindings(cx);
             if (!FillBindingVector(script, &bindings))
                 return false;
             for (size_t i = 0; i < fun->nargs; i++) {
@@ -4737,19 +4697,25 @@ DebuggerObject_getScript(JSContext *cx, 
     }
 
     RootedFunction fun(cx, &obj->as<JSFunction>());
     if (fun->isBuiltin()) {
         args.rval().setUndefined();
         return true;
     }
 
-    RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
-    if (!script)
-        return false;
+    RootedScript script(cx);
+
+    {
+        AutoCompartment ac(cx, obj);
+
+        script = fun->getOrCreateScript(cx);
+        if (!script)
+            return false;
+    }
 
     /* Only hand out debuggee scripts. */
     if (!dbg->observesScript(script)) {
         args.rval().setNull();
         return true;
     }
 
     RootedObject scriptObject(cx, dbg->wrapScript(cx, script));
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -225,22 +225,22 @@ class Debugger : private mozilla::Linked
     /* The map from debuggee Envs to Debugger.Environment instances. */
     ObjectWeakMap environments;
 
     class FrameRange;
     class ScriptQuery;
 
     bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
     bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
-                           AutoDebugModeInvalidation &invalidate);
+                           AutoDebugModeGC &dmgc);
     void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
                               GlobalObjectSet::Enum *compartmentEnum,
                               GlobalObjectSet::Enum *debugEnum);
     void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
-                              AutoDebugModeInvalidation &invalidate,
+                              AutoDebugModeGC &dmgc,
                               GlobalObjectSet::Enum *compartmentEnum,
                               GlobalObjectSet::Enum *debugEnum);
 
     /*
      * Cope with an error or exception in a debugger hook.
      *
      * If callHook is true, then call the uncaughtExceptionHook, if any. If, in
      * addition, vp is given, then parse the value returned by
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1407,65 +1407,62 @@ CASE(EnableInterruptsPseudoOpcode)
     }
 
     if (script->hasScriptCounts) {
         PCCounts counts = script->getPCCounts(REGS.pc);
         counts.get(PCCounts::BASE_INTERP)++;
         moreInterrupts = true;
     }
 
-    if (cx->compartment()->debugMode()) {
-        JSInterruptHook hook = cx->runtime()->debugHooks.interruptHook;
-        if (hook || script->stepModeEnabled()) {
-            RootedValue rval(cx);
-            JSTrapStatus status = JSTRAP_CONTINUE;
-            if (hook)
-                status = hook(cx, script, REGS.pc, rval.address(),
-                              cx->runtime()->debugHooks.interruptHookData);
-            if (status == JSTRAP_CONTINUE && script->stepModeEnabled())
-                status = Debugger::onSingleStep(cx, &rval);
-            switch (status) {
-              case JSTRAP_ERROR:
-                goto error;
-              case JSTRAP_CONTINUE:
-                break;
-              case JSTRAP_RETURN:
-                REGS.fp()->setReturnValue(rval);
-                interpReturnOK = true;
-                goto forced_return;
-              case JSTRAP_THROW:
-                cx->setPendingException(rval);
-                goto error;
-              default:;
-            }
-            moreInterrupts = true;
+    JSInterruptHook hook = cx->runtime()->debugHooks.interruptHook;
+    if (hook || script->stepModeEnabled()) {
+        RootedValue rval(cx);
+        JSTrapStatus status = JSTRAP_CONTINUE;
+        if (hook)
+            status = hook(cx, script, REGS.pc, rval.address(), cx->runtime()->debugHooks.interruptHookData);
+        if (status == JSTRAP_CONTINUE && script->stepModeEnabled())
+            status = Debugger::onSingleStep(cx, &rval);
+        switch (status) {
+          case JSTRAP_ERROR:
+            goto error;
+          case JSTRAP_CONTINUE:
+            break;
+          case JSTRAP_RETURN:
+            REGS.fp()->setReturnValue(rval);
+            interpReturnOK = true;
+            goto forced_return;
+          case JSTRAP_THROW:
+            cx->setPendingException(rval);
+            goto error;
+          default:;
         }
-
-        if (script->hasAnyBreakpointsOrStepMode())
-            moreInterrupts = true;
-
-        if (script->hasBreakpointsAt(REGS.pc)) {
-            RootedValue rval(cx);
-            JSTrapStatus status = Debugger::onTrap(cx, &rval);
-            switch (status) {
-              case JSTRAP_ERROR:
-                goto error;
-              case JSTRAP_RETURN:
-                REGS.fp()->setReturnValue(rval);
-                interpReturnOK = true;
-                goto forced_return;
-              case JSTRAP_THROW:
-                cx->setPendingException(rval);
-                goto error;
-              default:
-                break;
-            }
-            JS_ASSERT(status == JSTRAP_CONTINUE);
-            JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
+        moreInterrupts = true;
+    }
+
+    if (script->hasAnyBreakpointsOrStepMode())
+        moreInterrupts = true;
+
+    if (script->hasBreakpointsAt(REGS.pc)) {
+        RootedValue rval(cx);
+        JSTrapStatus status = Debugger::onTrap(cx, &rval);
+        switch (status) {
+          case JSTRAP_ERROR:
+            goto error;
+          case JSTRAP_RETURN:
+            REGS.fp()->setReturnValue(rval);
+            interpReturnOK = true;
+            goto forced_return;
+          case JSTRAP_THROW:
+            cx->setPendingException(rval);
+            goto error;
+          default:
+            break;
         }
+        JS_ASSERT(status == JSTRAP_CONTINUE);
+        JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
     }
 
     JS_ASSERT(activation.opMask() == EnableInterruptsPseudoOpcode);
     if (!moreInterrupts)
         activation.clearInterruptsMask();
 
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -165,36 +165,33 @@ js::DebugExceptionUnwind(JSContext *cx, 
     }
 
     return status;
 }
 
 JS_FRIEND_API(bool)
 JS_SetDebugModeForAllCompartments(JSContext *cx, bool debug)
 {
-    for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
-        // Invalidate a zone at a time to avoid doing a zone-wide CellIter
-        // per compartment.
-        AutoDebugModeInvalidation invalidate(zone);
-        for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
-            // Ignore special compartments (atoms, JSD compartments)
-            if (c->principals) {
-                if (!c->setDebugModeFromC(cx, !!debug, invalidate))
-                    return false;
-            }
+    AutoDebugModeGC dmgc(cx->runtime());
+
+    for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+        // Ignore special compartments (atoms, JSD compartments)
+        if (c->principals) {
+            if (!c->setDebugModeFromC(cx, !!debug, dmgc))
+                return false;
         }
     }
     return true;
 }
 
 JS_FRIEND_API(bool)
 JS_SetDebugModeForCompartment(JSContext *cx, JSCompartment *comp, bool debug)
 {
-    AutoDebugModeInvalidation invalidate(comp);
-    return comp->setDebugModeFromC(cx, !!debug, invalidate);
+    AutoDebugModeGC dmgc(cx->runtime());
+    return comp->setDebugModeFromC(cx, !!debug, dmgc);
 }
 
 static bool
 CheckDebugMode(JSContext *cx)
 {
     bool debugMode = JS_GetDebugMode(cx);
     /*
      * :TODO: