Bug 716647 - Part 5: Relax the no on-stack scripts restriction for addDebuggee. (r=jimb)
authorShu-yu Guo <shu@rfrn.org>
Thu, 24 Apr 2014 01:59:37 -0700
changeset 180266 c92f83e9a8640d2db1f0db49879caad5a937055a
parent 180265 a1354a3e748efbbdf62fa2b93b95eb80a4857324
child 180267 0e61e88866ceacf80007481e37a23427e6b1efa4
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjimb
bugs716647
milestone31.0a1
Bug 716647 - Part 5: Relax the no on-stack scripts restriction for addDebuggee. (r=jimb)
js/src/jit/BaselineBailouts.cpp
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/GlobalObject.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -7,17 +7,19 @@
 #include "jsprf.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
 #include "jit/CompileInfo.h"
 #include "jit/IonSpewer.h"
 #include "jit/Recover.h"
 #include "jit/RematerializedFrame.h"
+
 #include "vm/ArgumentsObject.h"
+#include "vm/Debugger.h"
 #include "vm/TraceLogging.h"
 
 #include "jsscriptinlines.h"
 
 #include "jit/IonFrames-inl.h"
 
 using namespace js;
 using namespace js::jit;
@@ -1531,42 +1533,47 @@ HandleBaselineInfoBailout(JSContext *cx,
             outerScript->filename(), outerScript->lineno());
 
     JS_ASSERT(!outerScript->ionScript()->invalidated());
 
     IonSpew(IonSpew_BaselineBailouts, "Invalidating due to invalid baseline info");
     return Invalidate(cx, outerScript);
 }
 
-static void
+static bool
 CopyFromRematerializedFrame(JSContext *cx, JitActivation *act, uint8_t *fp, size_t inlineDepth,
                             BaselineFrame *frame)
 {
     RematerializedFrame *rematFrame = act->lookupRematerializedFrame(fp, inlineDepth);
 
     // We might not have rematerialized a frame if the user never requested a
     // Debugger.Frame for it.
     if (!rematFrame)
-        return;
+        return true;
 
     MOZ_ASSERT(rematFrame->script() == frame->script());
     MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs());
 
     frame->setScopeChain(rematFrame->scopeChain());
     frame->thisValue() = rematFrame->thisValue();
 
     for (unsigned i = 0; i < frame->numActualArgs(); i++)
         frame->argv()[i] = rematFrame->argv()[i];
 
     for (size_t i = 0; i < frame->script()->nfixed(); i++)
         *frame->valueSlot(i) = rematFrame->locals()[i];
 
     IonSpew(IonSpew_BaselineBailouts,
             "  Copied from rematerialized frame at (%p,%u)",
             fp, inlineDepth);
+
+    if (cx->compartment()->debugMode())
+        return Debugger::handleIonBailout(cx, rematFrame, frame);
+
+    return true;
 }
 
 uint32_t
 jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo)
 {
     // The caller pushes R0 and R1 on the stack without rooting them.
     // Since GC here is very unlikely just suppress it.
     JSContext *cx = GetJSContextFromJitCode();
@@ -1656,19 +1663,21 @@ jit::FinishBailoutToBaseline(BaselineBai
     // values into the baseline frame. We need to do this even when debug mode
     // is off, as we should respect the mutations made while debug mode was
     // on.
     JitActivation *act = cx->mainThread().activation()->asJit();
     if (act->hasRematerializedFrame(outerFp)) {
         JitFrameIterator iter(cx);
         size_t inlineDepth = numFrames;
         while (inlineDepth > 0) {
-            if (iter.isBaselineJS()) {
-                inlineDepth--;
-                CopyFromRematerializedFrame(cx, act, outerFp, inlineDepth, iter.baselineFrame());
+            if (iter.isBaselineJS() &&
+                !CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
+                                             iter.baselineFrame()))
+            {
+                return false;
             }
             ++iter;
         }
 
         // After copying from all the rematerialized frames, remove them from
         // the table to keep the table up to date.
         act->removeRematerializedFrame(outerFp);
     }
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -3031,16 +3031,38 @@ jit::RematerializeAllFrames(JSContext *c
                 if (!iter.activation()->asJit()->getRematerializedFrame(cx, frameIter))
                     return false;
             }
         }
     }
     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()
 {
     MOZ_ASSERT(!!comp_ != !!zone_);
 
     if (needInvalidation_ == NoNeed)
         return;
 
     Zone *zone = zone_ ? zone_ : comp_->zone();
@@ -3049,31 +3071,33 @@ AutoDebugModeInvalidation::~AutoDebugMod
 
     if (comp_) {
         StopAllOffThreadCompilations(comp_);
     } else {
         for (CompartmentsInZoneIter comp(zone_); !comp.done(); comp.next())
             StopAllOffThreadCompilations(comp);
     }
 
+    // Don't discard active baseline scripts. They are recompiled for debug
+    // mode.
     jit::MarkActiveBaselineScripts(zone);
 
     for (JitActivationIterator iter(rt); !iter.done(); ++iter) {
         JSCompartment *comp = iter.activation()->compartment();
-        if ((comp_ && comp_ == comp) || (zone_ && zone_ == comp->zone())) {
+        if (comp_ == comp || zone_ == comp->zone()) {
             IonContext ictx(CompileRuntime::get(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_) {
+        if (script->compartment() == comp_ || zone_) {
             FinishInvalidation<SequentialExecution>(fop, script);
             FinishInvalidation<ParallelExecution>(fop, script);
             FinishDiscardBaselineScript(fop, script);
             script->resetUseCount();
         } else if (script->hasBaselineScript()) {
             script->baselineScript()->resetActive();
         }
     }
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -184,15 +184,17 @@ void ForbidCompilation(JSContext *cx, JS
 void PurgeCaches(JSScript *script, JS::Zone *zone);
 size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf);
 void DestroyIonScripts(FreeOp *fop, JSScript *script);
 void TraceIonScripts(JSTracer* trc, JSScript *script);
 
 void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode);
 
 bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp);
+bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp,
+                        AutoDebugModeInvalidation &invalidate);
 
 } // namespace jit
 } // namespace js
 
 #endif // JS_ION
 
 #endif /* jit_Ion_h */
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -761,68 +761,57 @@ JSCompartment::ensureDelazifyScriptsForD
 }
 
 bool
 JSCompartment::setDebugModeFromC(JSContext *cx, bool b, AutoDebugModeInvalidation &invalidate)
 {
     bool enabledBefore = debugMode();
     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
-    // the non-live scripts' JITScripts because they might share ICs with live
-    // scripts (bug 632343).
+    // Enabling debug mode from C (vs of from JS) can only be done when no
+    // scripts from the target compartment are on the stack.
     //
     // 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
     // hooks may be called erroneously, even though debug mode is supposedly
     // off, and we have to live with it.
-    //
     bool onStack = false;
     if (enabledBefore != enabledAfter) {
         onStack = hasScriptsOnStack();
         if (b && onStack) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
     }
 
     debugModeBits = (debugModeBits & ~DebugFromC) | (b ? DebugFromC : 0);
     JS_ASSERT(debugMode() == 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)
             DebugScopes::onCompartmentLeaveDebugMode(this);
     }
     return true;
 }
 
-void
-JSCompartment::updateForDebugMode(FreeOp *fop, AutoDebugModeInvalidation &invalidate)
+bool
+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
-    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
     // 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());
+    if (!jit::UpdateForDebugMode(maybecx, this, invalidate))
+        return false;
 #endif
+    return true;
 }
 
 bool
 JSCompartment::addDebuggee(JSContext *cx, js::GlobalObject *global)
 {
     AutoDebugModeInvalidation invalidate(this);
     return addDebuggee(cx, global, invalidate);
 }
@@ -835,49 +824,69 @@ JSCompartment::addDebuggee(JSContext *cx
     Rooted<GlobalObject*> global(cx, globalArg);
 
     bool wasEnabled = debugMode();
     if (!debuggees.put(global)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     debugModeBits |= DebugFromJS;
-    if (!wasEnabled)
-        updateForDebugMode(cx->runtime()->defaultFreeOp(), invalidate);
+    if (!wasEnabled && !updateJITForDebugMode(cx, invalidate))
+        return false;
+    return true;
+}
+
+bool
+JSCompartment::removeDebuggee(JSContext *cx,
+                              js::GlobalObject *global,
+                              js::GlobalObjectSet::Enum *debuggeesEnum)
+{
+    AutoDebugModeInvalidation invalidate(this);
+    return removeDebuggee(cx, global, invalidate, debuggeesEnum);
+}
+
+bool
+JSCompartment::removeDebuggee(JSContext *cx,
+                              js::GlobalObject *global,
+                              AutoDebugModeInvalidation &invalidate,
+                              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::removeDebuggee(FreeOp *fop,
-                              js::GlobalObject *global,
-                              js::GlobalObjectSet::Enum *debuggeesEnum)
+JSCompartment::removeDebuggeeUnderGC(FreeOp *fop,
+                                     js::GlobalObject *global,
+                                     js::GlobalObjectSet::Enum *debuggeesEnum)
 {
     AutoDebugModeInvalidation invalidate(this);
-    return removeDebuggee(fop, global, invalidate, debuggeesEnum);
+    removeDebuggeeUnderGC(fop, global, invalidate, debuggeesEnum);
 }
 
 void
-JSCompartment::removeDebuggee(FreeOp *fop,
-                              js::GlobalObject *global,
-                              AutoDebugModeInvalidation &invalidate,
-                              js::GlobalObjectSet::Enum *debuggeesEnum)
+JSCompartment::removeDebuggeeUnderGC(FreeOp *fop,
+                                     js::GlobalObject *global,
+                                     AutoDebugModeInvalidation &invalidate,
+                                     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()) {
+        if (wasEnabled && !debugMode())
             DebugScopes::onCompartmentLeaveDebugMode(this);
-            updateForDebugMode(fop, invalidate);
-        }
     }
 }
 
 void
 JSCompartment::clearBreakpointsIn(FreeOp *fop, js::Debugger *dbg, JSObject *handler)
 {
     for (gc::CellIter i(zone(), gc::FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -390,28 +390,33 @@ struct JSCompartment
      * 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);
+    bool updateJITForDebugMode(JSContext *maybecx, js::AutoDebugModeInvalidation &invalidate);
 
   public:
     js::GlobalObjectSet &getDebuggees() { return debuggees; }
     bool addDebuggee(JSContext *cx, js::GlobalObject *global);
     bool addDebuggee(JSContext *cx, js::GlobalObject *global,
                      js::AutoDebugModeInvalidation &invalidate);
-    void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global,
+    bool removeDebuggee(JSContext *cx, js::GlobalObject *global,
                         js::GlobalObjectSet::Enum *debuggeesEnum = nullptr);
-    void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global,
+    bool removeDebuggee(JSContext *cx, js::GlobalObject *global,
                         js::AutoDebugModeInvalidation &invalidate,
                         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,
                            js::AutoDebugModeInvalidation &invalidate);
 
     void clearBreakpointsIn(js::FreeOp *fop, js::Debugger *dbg, JSObject *handler);
     void clearTraps(js::FreeOp *fop);
 
   private:
     void sweepBreakpoints(js::FreeOp *fop);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -1650,42 +1650,48 @@ Debugger::sweepAll(FreeOp *fop)
 
     for (Debugger *dbg = rt->debuggerList.getFirst(); dbg; dbg = dbg->getNext()) {
         if (IsObjectAboutToBeFinalized(&dbg->object)) {
             /*
              * dbg is being GC'd. Detach it from its debuggees. The debuggee
              * might be GC'd too. Since detaching requires access to both
              * objects, this must be done before finalize time.
              */
-            for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
-                dbg->removeDebuggeeGlobal(fop, e.front(), nullptr, &e);
+            for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
+                // 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);
+            }
         }
     }
 
     for (gc::GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) {
         /* For each debuggee being GC'd, detach it from all its debuggers. */
         GlobalObjectSet &debuggees = comp->getDebuggees();
         for (GlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront()) {
             GlobalObject *global = e.front();
-            if (IsObjectAboutToBeFinalized(&global))
+            if (IsObjectAboutToBeFinalized(&global)) {
+                // See infallibility note above.
                 detachAllDebuggersFromGlobal(fop, global, &e);
+            }
             else if (global != e.front())
                 e.rekeyFront(global);
         }
     }
 }
 
 void
 Debugger::detachAllDebuggersFromGlobal(FreeOp *fop, GlobalObject *global,
                                        GlobalObjectSet::Enum *compartmentEnum)
 {
     const GlobalObject::DebuggerVector *debuggers = global->getDebuggers();
     JS_ASSERT(!debuggers->empty());
     while (!debuggers->empty())
-        debuggers->back()->removeDebuggeeGlobal(fop, global, compartmentEnum, nullptr);
+        debuggers->back()->removeDebuggeeGlobalUnderGC(fop, global, compartmentEnum, nullptr);
 }
 
 /* static */ void
 Debugger::findCompartmentEdges(Zone *zone, js::gc::ComponentFinder<Zone> &finder)
 {
     /*
      * For debugger cross compartment wrappers, add edges in the opposite
      * direction to those already added by JSCompartment::findOutgoingEdges.
@@ -2013,16 +2019,17 @@ Debugger::addDebuggee(JSContext *cx, uns
 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))
@@ -2038,28 +2045,33 @@ Debugger::addAllGlobalsAsDebuggees(JSCon
 bool
 Debugger::removeDebuggee(JSContext *cx, unsigned argc, Value *vp)
 {
     REQUIRE_ARGC("Debugger.removeDebuggee", 1);
     THIS_DEBUGGER(cx, argc, vp, "removeDebuggee", args, dbg);
     GlobalObject *global = dbg->unwrapDebuggeeArgument(cx, args[0]);
     if (!global)
         return false;
-    if (dbg->debuggees.has(global))
-        dbg->removeDebuggeeGlobal(cx->runtime()->defaultFreeOp(), global, nullptr, nullptr);
+    if (dbg->debuggees.has(global)) {
+        if (!dbg->removeDebuggeeGlobal(cx, global, nullptr, nullptr))
+            return false;
+    }
     args.rval().setUndefined();
     return true;
 }
 
 bool
 Debugger::removeAllDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     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();
     return true;
 }
 
 bool
 Debugger::hasDebuggee(JSContext *cx, unsigned argc, Value *vp)
 {
@@ -2093,25 +2105,24 @@ Debugger::getDebuggees(JSContext *cx, un
 
 bool
 Debugger::getNewestFrame(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
 
     /* Since there may be multiple contexts, use AllFramesIter. */
     for (AllFramesIter i(cx); !i.done(); ++i) {
-        /*
-         * Debug-mode currently disables Ion compilation in the compartment of
-         * the debuggee.
-         */
-        if (i.isIon())
-            continue;
-        if (dbg->observesFrame(i.abstractFramePtr())) {
+        if (dbg->observesFrame(i)) {
+            // Ensure that Ion frames are rematerialized. Only rematerialized
+            // Ion frames may be used as AbstractFramePtrs.
+            if (i.isIon() && !i.ensureHasRematerializedFrame())
+                return false;
+            AbstractFramePtr frame = i.abstractFramePtr();
             ScriptFrameIter iter(i.activation()->cx(), ScriptFrameIter::GO_THROUGH_SAVED);
-            while (iter.isIon() || iter.abstractFramePtr() != i.abstractFramePtr())
+            while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
                 ++iter;
             return dbg->getScriptFrame(cx, iter, args.rval());
         }
     }
     args.rval().setNull();
     return true;
 }
 
@@ -2243,22 +2254,16 @@ Debugger::addDebuggeeGlobal(JSContext *c
             for (Debugger **p = v->begin(); p != v->end(); p++) {
                 JSCompartment *next = (*p)->object->compartment();
                 if (Find(visited, next) == visited.end() && !visited.append(next))
                     return false;
             }
         }
     }
 
-    /* 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.
      * JSCompartment::addDebuggee enables debug mode if needed.
      */
     AutoCompartment ac(cx, global);
     GlobalObject::DebuggerVector *v = GlobalObject::getOrCreateDebuggers(cx, global);
     if (!v || !v->append(this)) {
         js_ReportOutOfMemory(cx);
@@ -2276,29 +2281,20 @@ Debugger::addDebuggeeGlobal(JSContext *c
         }
         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());
-    removeDebuggeeGlobal(fop, global, invalidate, compartmentEnum, debugEnum);
-}
-
-void
-Debugger::removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
-                               AutoDebugModeInvalidation &invalidate,
-                               GlobalObjectSet::Enum *compartmentEnum,
-                               GlobalObjectSet::Enum *debugEnum)
+Debugger::cleanupDebuggeeGlobalBeforeRemoval(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
      * 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.
      */
     JS_ASSERT(global->compartment()->getDebuggees().has(global));
@@ -2346,24 +2342,67 @@ Debugger::removeDebuggeeGlobal(FreeOp *f
     /* Remove all breakpoints for the debuggee. */
     Breakpoint *nextbp;
     for (Breakpoint *bp = firstBreakpoint(); bp; bp = nextbp) {
         nextbp = bp->nextInDebugger();
         if (bp->site->script->compartment() == global->compartment())
             bp->destroy(fop);
     }
     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
      * 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);
+    if (global->getDebuggers()->empty())
+        global->compartment()->removeDebuggeeUnderGC(fop, global, invalidate, compartmentEnum);
 }
 
 /*
  * A class for parsing 'findScripts' query arguments and searching for
  * scripts that match the criteria they represent.
  */
 class Debugger::ScriptQuery {
   public:
@@ -3556,31 +3595,35 @@ DebuggerScript_getLineOffsets(JSContext 
 
 bool
 Debugger::observesFrame(AbstractFramePtr frame) const
 {
     return observesScript(frame.script());
 }
 
 bool
+Debugger::observesFrame(const ScriptFrameIter &iter) const
+{
+    return observesScript(iter.script());
+}
+
+bool
 Debugger::observesScript(JSScript *script) const
 {
     if (!enabled)
         return false;
     return observesGlobal(&script->global()) && (!script->selfHosted() ||
                                                  SelfHostedFramesVisible());
 }
 
 /* static */ bool
-Debugger::handleBaselineOsr(JSContext *cx, InterpreterFrame *from, jit::BaselineFrame *to)
-{
-    ScriptFrameIter iter(cx);
-    JS_ASSERT(iter.abstractFramePtr() == to);
-
-    for (FrameRange r(from); !r.empty(); r.popFront()) {
+Debugger::replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
+                           ScriptFrameIter &iter)
+{
+    for (Debugger::FrameRange r(from); !r.empty(); r.popFront()) {
         RootedObject frameobj(cx, r.frontFrame());
         Debugger *dbg = r.frontDebugger();
         JS_ASSERT(dbg == Debugger::fromChildJSObject(frameobj));
 
         // Update frame object's ScriptFrameIter::data pointer.
         DebuggerFrame_freeScriptFrameIterData(cx->runtime()->defaultFreeOp(), frameobj);
         ScriptFrameIter::Data *data = iter.copyData();
         if (!data)
@@ -3595,16 +3638,41 @@ Debugger::handleBaselineOsr(JSContext *c
             js_ReportOutOfMemory(cx);
             return false;
         }
     }
 
     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
 DebuggerScript_setBreakpoint(JSContext *cx, unsigned argc, Value *vp)
 {
     REQUIRE_ARGC("Debugger.Script.setBreakpoint", 2);
     THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
     Debugger *dbg = Debugger::fromChildJSObject(obj);
 
     if (!dbg->observesScript(script)) {
@@ -4029,16 +4097,53 @@ static const JSPropertySpec DebuggerSour
 static const JSFunctionSpec DebuggerSource_methods[] = {
     JS_FS_END
 };
 
 
 /*** 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
 DebuggerFrame_freeScriptFrameIterData(FreeOp *fop, JSObject *obj)
 {
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->getPrivate());
     if (frame.isScriptFrameIterData())
         fop->delete_((ScriptFrameIter::Data *) frame.raw());
     obj->setPrivate(nullptr);
 }
 
@@ -4171,24 +4276,45 @@ DebuggerFrame_getType(JSContext *cx, uns
                           ? cx->names().eval
                           : frame.isGlobalFrame()
                           ? cx->names().global
                           : cx->names().call);
     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
 DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg);
 
     Rooted<Env*> env(cx);
     {
         AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
-        iter.updatePcQuadratic();
+        UpdateFrameIterPc(iter);
         env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
         if (!env)
             return false;
     }
 
     return dbg->wrapEnvironment(cx, env, args.rval());
 }
 
@@ -4238,20 +4364,21 @@ DebuggerFrame_getThis(JSContext *cx, uns
 
 static bool
 DebuggerFrame_getOlder(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_ITER(cx, argc, vp, "get this", args, thisobj, _, iter);
     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
 
     for (++iter; !iter.done(); ++iter) {
-        if (iter.isIon())
-            continue;
-        if (dbg->observesFrame(iter.abstractFramePtr()))
+        if (dbg->observesFrame(iter)) {
+            if (iter.isIon() && !iter.ensureHasRematerializedFrame())
+                return false;
             return dbg->getScriptFrame(cx, iter, args.rval());
+        }
     }
     args.rval().setNull();
     return true;
 }
 
 const Class DebuggerArguments_class = {
     "Arguments", JSCLASS_HAS_RESERVED_SLOTS(JSSLOT_DEBUGARGUMENTS_COUNT),
     JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
@@ -4403,17 +4530,17 @@ DebuggerFrame_getScript(JSContext *cx, u
     return true;
 }
 
 static bool
 DebuggerFrame_getOffset(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_ITER(cx, argc, vp, "get offset", args, thisobj, _, iter);
     JSScript *script = iter.script();
-    iter.updatePcQuadratic();
+    UpdateFrameIterPc(iter);
     jsbytecode *pc = iter.pc();
     size_t offset = script->pcToOffset(pc);
     args.rval().setNumber(double(offset));
     return true;
 }
 
 static bool
 DebuggerFrame_getLive(JSContext *cx, unsigned argc, Value *vp)
@@ -4678,29 +4805,29 @@ DebuggerGenericEval(JSContext *cx, const
 }
 
 static bool
 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
     REQUIRE_ARGC("Debugger.Frame.prototype.eval", 1);
     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
-    iter.updatePcQuadratic();
+    UpdateFrameIterPc(iter);
     return DebuggerGenericEval(cx, "Debugger.Frame.prototype.eval",
                                args[0], EvalWithDefaultBindings, JS::UndefinedHandleValue,
                                args.get(1), args.rval(), dbg, js::NullPtr(), &iter);
 }
 
 static bool
 DebuggerFrame_evalWithBindings(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_ITER(cx, argc, vp, "evalWithBindings", args, thisobj, _, iter);
     REQUIRE_ARGC("Debugger.Frame.prototype.evalWithBindings", 2);
     Debugger *dbg = Debugger::fromChildJSObject(thisobj);
-    iter.updatePcQuadratic();
+    UpdateFrameIterPc(iter);
     return DebuggerGenericEval(cx, "Debugger.Frame.prototype.evalWithBindings",
                                args[0], EvalHasExtraBindings, args[1], args.get(2),
                                args.rval(), dbg, js::NullPtr(), &iter);
 }
 
 static bool
 DebuggerFrame_construct(JSContext *cx, unsigned argc, Value *vp)
 {
@@ -4716,16 +4843,17 @@ static const JSPropertySpec DebuggerFram
     JS_PSG("environment", DebuggerFrame_getEnvironment, 0),
     JS_PSG("generator", DebuggerFrame_getGenerator, 0),
     JS_PSG("live", DebuggerFrame_getLive, 0),
     JS_PSG("offset", DebuggerFrame_getOffset, 0),
     JS_PSG("older", DebuggerFrame_getOlder, 0),
     JS_PSG("script", DebuggerFrame_getScript, 0),
     JS_PSG("this", DebuggerFrame_getThis, 0),
     JS_PSG("type", DebuggerFrame_getType, 0),
+    JS_PSG("implementation", DebuggerFrame_getImplementation, 0),
     JS_PSGS("onStep", DebuggerFrame_getOnStep, DebuggerFrame_setOnStep, 0),
     JS_PSGS("onPop", DebuggerFrame_getOnPop, DebuggerFrame_setOnPop, 0),
     JS_PS_END
 };
 
 static const JSFunctionSpec DebuggerFrame_methods[] = {
     JS_FN("eval", DebuggerFrame_eval, 1, 0),
     JS_FN("evalWithBindings", DebuggerFrame_evalWithBindings, 1, 0),
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -234,23 +234,34 @@ class Debugger : private mozilla::Linked
     ObjectWeakMap environments;
 
     class FrameRange;
     class ScriptQuery;
 
     bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj);
     bool addDebuggeeGlobal(JSContext *cx, Handle<GlobalObject*> obj,
                            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 *debugEnum);
-    void removeDebuggeeGlobal(FreeOp *fop, GlobalObject *global,
+    bool removeDebuggeeGlobal(JSContext *cx, GlobalObject *global,
                               AutoDebugModeInvalidation &invalidate,
                               GlobalObjectSet::Enum *compartmentEnum,
                               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.
      *
      * If callHook is true, then call the uncaughtExceptionHook, if any. If, in
      * addition, vp is given, then parse the value returned by
      * uncaughtExceptionHook as a resumption value.
      *
@@ -377,16 +388,19 @@ class Debugger : private mozilla::Linked
      */
     bool getScriptFrameWithIter(JSContext *cx, AbstractFramePtr frame,
                                 const ScriptFrameIter *maybeIter, MutableHandleValue vp);
 
     inline Breakpoint *firstBreakpoint() const;
 
     static inline Debugger *fromOnNewGlobalObjectWatchersLink(JSCList *link);
 
+    static bool replaceFrameGuts(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to,
+                                 ScriptFrameIter &iter);
+
   public:
     Debugger(JSContext *cx, JSObject *dbg);
     ~Debugger();
 
     bool init(JSContext *cx);
     inline const js::HeapPtrObject &toJSObject() const;
     inline js::HeapPtrObject &toJSObjectRef();
     static inline Debugger *fromJSObject(JSObject *obj);
@@ -423,24 +437,26 @@ class Debugger : private mozilla::Linked
     static inline JSTrapStatus onDebuggerStatement(JSContext *cx, MutableHandleValue vp);
     static inline JSTrapStatus onExceptionUnwind(JSContext *cx, MutableHandleValue vp);
     static inline void onNewScript(JSContext *cx, HandleScript script,
                                    GlobalObject *compileAndGoGlobal);
     static inline void onNewGlobalObject(JSContext *cx, Handle<GlobalObject *> global);
     static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp);
     static JSTrapStatus onSingleStep(JSContext *cx, MutableHandleValue vp);
     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. */
 
     inline bool observesEnterFrame() const;
     inline bool observesNewScript() const;
     inline bool observesNewGlobalObject() const;
     inline bool observesGlobal(GlobalObject *global) const;
     bool observesFrame(AbstractFramePtr frame) const;
+    bool observesFrame(const ScriptFrameIter &iter) const;
     bool observesScript(JSScript *script) const;
 
     /*
      * If env is nullptr, call vp->setNull() and return true. Otherwise, find
      * or create a Debugger.Environment object for the given Env. On success,
      * store the Environment object in *vp and return true.
      */
     bool wrapEnvironment(JSContext *cx, Handle<Env*> env, MutableHandleValue vp);
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -757,17 +757,17 @@ GlobalObject::addDebugger(JSContext *cx,
         return false;
 #ifdef DEBUG
     for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++)
         JS_ASSERT(*p != dbg);
 #endif
     if (debuggers->empty() && !global->compartment()->addDebuggee(cx, global))
         return false;
     if (!debuggers->append(dbg)) {
-        global->compartment()->removeDebuggee(cx->runtime()->defaultFreeOp(), global);
+        (void) global->compartment()->removeDebuggee(cx, global);
         return false;
     }
     return true;
 }
 
 /* static */ JSObject *
 GlobalObject::getOrCreateForOfPICObject(JSContext *cx, Handle<GlobalObject *> global)
 {
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1054,34 +1054,66 @@ FrameIter::isConstructing() const
         break;
 #endif
       case INTERP:
         return interpFrame()->isConstructing();
     }
     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
 FrameIter::abstractFramePtr() const
 {
+    MOZ_ASSERT(hasUsableAbstractFramePtr());
     switch (data_.state_) {
       case DONE:
       case ASMJS:
         break;
       case JIT: {
 #ifdef JS_ION
         if (data_.jitFrames_.isBaselineJS())
             return data_.jitFrames_.baselineFrame();
 
         MOZ_ASSERT(data_.jitFrames_.isIonJS());
-        jit::RematerializedFrame *frame =
-            activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
-                                                             ionInlineFrames_.frameNo());
-        MOZ_ASSERT(frame);
-        return frame;
+        return activation()->asJit()->lookupRematerializedFrame(data_.jitFrames_.fp(),
+                                                                ionInlineFrames_.frameNo());
 #endif
         break;
       }
       case INTERP:
         JS_ASSERT(interpFrame());
         return AbstractFramePtr(interpFrame());
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1661,19 +1661,29 @@ class FrameIter
     JSFunction *maybeCallee() const {
         return isFunctionFrame() ? callee() : nullptr;
     }
 
     // These are only valid for the top frame.
     size_t      numFrameSlots() const;
     Value       frameSlotValue(size_t index) const;
 
-    // --------------------------------------------------------------------------
-    // The following functions can only be called when isInterp() or isBaseline()
-    // --------------------------------------------------------------------------
+    // Ensures that we have rematerialized the top frame and its associated
+    // 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 copyDataAsAbstractFramePtr() const;
     Data *copyData() const;
 
     // This can only be called when isInterp():
     inline InterpreterFrame *interpFrame() const;