Bug 927782 - Part 10: Iterate block chain from compile-time block scope maps, not runtime blockChain. r=luke
☠☠ backed out by 94cdaced90bf ☠ ☠
authorAndy Wingo <wingo@igalia.com>
Fri, 06 Dec 2013 18:42:08 +0100
changeset 174947 eed9795fa80ea7d960962db8cc7d228137cffe54
parent 174946 b971de7edfff874f74137a241e352ed87d6d14a3
child 174948 51d6617835d140affaf45ed9787d317388beb1ff
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs927782
milestone28.0a1
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
Bug 927782 - Part 10: Iterate block chain from compile-time block scope maps, not runtime blockChain. r=luke
js/jsd/jsd_stak.cpp
js/public/OldDebugAPI.h
js/src/jit/BaselineCompiler.cpp
js/src/jit/IonFrames.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/OldDebugAPI.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack.h
--- a/js/jsd/jsd_stak.cpp
+++ b/js/jsd/jsd_stak.cpp
@@ -92,17 +92,17 @@ jsd_NewThreadState(JSDContext* jsdc, JSC
 
     JS_BeginRequest(jsdthreadstate->context);
 
     JSBrokenFrameIterator iter(cx);
     while(!iter.done())
     {
         JSAbstractFramePtr frame = iter.abstractFramePtr();
         JS::RootedScript script(cx, frame.script());
-        uintptr_t  pc = (uintptr_t)iter.pc();
+        uintptr_t  pc = (uintptr_t)frame.pc();
         JS::RootedValue dummyThis(cx);
 
         /*
          * don't construct a JSDStackFrame for dummy frames (those without a
          * |this| object, or native frames, if JSD_INCLUDE_NATIVE_FRAMES
          * isn't set.
          */
         if (frame.getThisValue(cx, &dummyThis))
--- a/js/public/OldDebugAPI.h
+++ b/js/public/OldDebugAPI.h
@@ -334,26 +334,28 @@ JS_PutPropertyDescArray(JSContext *cx, J
 
 /*
  * JSAbstractFramePtr is the public version of AbstractFramePtr, a pointer to a
  * StackFrame or baseline JIT frame.
  */
 class JS_PUBLIC_API(JSAbstractFramePtr)
 {
     uintptr_t ptr_;
+    jsbytecode *pc_;
 
   protected:
     JSAbstractFramePtr()
-      : ptr_(0)
+      : ptr_(0), pc_(nullptr)
     { }
 
   public:
-    explicit JSAbstractFramePtr(void *raw);
+    JSAbstractFramePtr(void *raw, jsbytecode *pc);
 
     uintptr_t raw() const { return ptr_; }
+    jsbytecode *pc() const { return pc_; }
 
     operator bool() const { return !!ptr_; }
 
     JSObject *scopeChain(JSContext *cx);
     JSObject *callObject(JSContext *cx);
 
     JSFunction *maybeFun();
     JSScript *script();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -503,29 +503,30 @@ BaselineCompiler::emitStackCheck(bool ea
 
     if (!callVM(CheckOverRecursedWithExtraInfo, phase))
         return false;
 
     masm.bind(&skipCall);
     return true;
 }
 
-typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, bool *);
+typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, jsbytecode *, bool *);
 static const VMFunction DebugPrologueInfo = FunctionInfo<DebugPrologueFn>(jit::DebugPrologue);
 
 bool
 BaselineCompiler::emitDebugPrologue()
 {
     if (!debugMode_)
         return true;
 
     // Load pointer to BaselineFrame in R0.
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     prepareVMCall();
+    pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
     if (!callVM(DebugPrologueInfo))
         return false;
 
     // If the stub returns |true|, we have to return the value stored in the
     // frame's return value slot.
     Label done;
     masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
@@ -2594,26 +2595,28 @@ BaselineCompiler::emit_JSOP_ENTERLET1()
 }
 
 bool
 BaselineCompiler::emit_JSOP_ENTERLET2()
 {
     return emitEnterBlock();
 }
 
-typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *);
+typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *);
 static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);
 
 bool
 BaselineCompiler::emit_JSOP_DEBUGLEAVEBLOCK()
 {
-    // Call a stub to pop the block from the block chain.
+    if (!debugMode_)
+        return true;
+
     prepareVMCall();
-
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+    pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
 
     return callVM(DebugLeaveBlockInfo);
 }
 
 typedef bool (*LeaveBlockFn)(JSContext *, BaselineFrame *);
 static const VMFunction LeaveBlockInfo = FunctionInfo<LeaveBlockFn>(jit::LeaveBlock);
 
@@ -2702,33 +2705,34 @@ BaselineCompiler::emit_JSOP_DEBUGGER()
     {
         masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
         masm.jump(&return_);
     }
     masm.bind(&done);
     return true;
 }
 
-typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, bool);
+typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, jsbytecode *, bool);
 static const VMFunction DebugEpilogueInfo = FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogue);
 
 bool
 BaselineCompiler::emitReturn()
 {
     if (debugMode_) {
         // Move return value into the frame's rval slot.
         masm.storeValue(JSReturnOperand, frame.addressOfReturnValue());
         masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags());
 
         // Load BaselineFrame pointer in R0.
         frame.syncStack(0);
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
         prepareVMCall();
         pushArg(Imm32(1));
+        pushArg(ImmPtr(pc));
         pushArg(R0.scratchReg());
         if (!callVM(DebugEpilogueInfo))
             return false;
 
         masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
     }
 
     // Only emit the jump if this JSOP_RETRVAL is not the last instruction.
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -429,17 +429,17 @@ HandleExceptionBaseline(JSContext *cx, c
 
           case JSTRAP_CONTINUE:
           case JSTRAP_THROW:
             JS_ASSERT(cx->isExceptionPending());
             break;
 
           case JSTRAP_RETURN:
             JS_ASSERT(baselineFrame->hasReturnValue());
-            if (jit::DebugEpilogue(cx, baselineFrame, true)) {
+            if (jit::DebugEpilogue(cx, baselineFrame, pc, true)) {
                 rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
                 rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
                 rfe->stackPointer = reinterpret_cast<uint8_t *>(baselineFrame);
                 return;
             }
 
             // DebugEpilogue threw an exception. Propagate to the caller frame.
             *calledDebugEpilogue = true;
@@ -452,32 +452,33 @@ HandleExceptionBaseline(JSContext *cx, c
 
     if (!script->hasTrynotes())
         return;
 
     JSTryNote *tn = script->trynotes()->vector;
     JSTryNote *tnEnd = tn + script->trynotes()->length;
 
     uint32_t pcOffset = uint32_t(pc - script->main());
+    ScopeIter si(frame.baselineFrame(), pc, cx);
     for (; tn != tnEnd; ++tn) {
         if (pcOffset < tn->start)
             continue;
         if (pcOffset >= tn->start + tn->length)
             continue;
 
         // Skip if the try note's stack depth exceeds the frame's stack depth.
         // See the big comment in TryNoteIter::settle for more info.
         JS_ASSERT(frame.baselineFrame()->numValueSlots() >= script->nfixed);
         size_t stackDepth = frame.baselineFrame()->numValueSlots() - script->nfixed;
         if (tn->stackDepth > stackDepth)
             continue;
 
         // Unwind scope chain (pop block objects).
         if (cx->isExceptionPending())
-            UnwindScope(cx, frame.baselineFrame(), tn->stackDepth);
+            UnwindScope(cx, si, tn->stackDepth);
 
         // Compute base pointer and stack pointer.
         rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
         rfe->stackPointer = rfe->framePointer - BaselineFrame::Size() -
             (script->nfixed + tn->stackDepth) * sizeof(Value);
 
         switch (tn->kind) {
           case JSTRY_CATCH:
@@ -602,17 +603,20 @@ HandleException(ResumeFromException *rfe
             // to be.  Unset the flag here so that if we call DebugEpilogue below,
             // it doesn't try to pop the SPS frame again.
             iter.baselineFrame()->unsetPushedSPSFrame();
  
             if (cx->compartment()->debugMode() && !calledDebugEpilogue) {
                 // If DebugEpilogue returns |true|, we have to perform a forced
                 // return, e.g. return frame->returnValue() to the caller.
                 BaselineFrame *frame = iter.baselineFrame();
-                if (jit::DebugEpilogue(cx, frame, false)) {
+                RootedScript script(cx);
+                jsbytecode *pc;
+                iter.baselineScriptAndPc(script.address(), &pc);
+                if (jit::DebugEpilogue(cx, frame, pc, false)) {
                     JS_ASSERT(frame->hasReturnValue());
                     rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
                     rfe->framePointer = iter.fp() - BaselineFrame::FramePointerOffset;
                     rfe->stackPointer = reinterpret_cast<uint8_t *>(frame);
                     return;
                 }
             }
         }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -690,51 +690,52 @@ GetIndexFromString(JSString *str)
     JSAtom *atom = &str->asAtom();
     if (!atom->isIndex(&index))
         return UINT32_MAX;
 
     return index;
 }
 
 bool
-DebugPrologue(JSContext *cx, BaselineFrame *frame, bool *mustReturn)
+DebugPrologue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn)
 {
     *mustReturn = false;
 
-    JSTrapStatus status = ScriptDebugPrologue(cx, frame);
+    JSTrapStatus status = ScriptDebugPrologue(cx, frame, pc);
     switch (status) {
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         // The script is going to return immediately, so we have to call the
         // debug epilogue handler as well.
         JS_ASSERT(frame->hasReturnValue());
         *mustReturn = true;
-        return jit::DebugEpilogue(cx, frame, true);
+        return jit::DebugEpilogue(cx, frame, pc, true);
 
       case JSTRAP_THROW:
       case JSTRAP_ERROR:
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
 }
 
 bool
-DebugEpilogue(JSContext *cx, BaselineFrame *frame, bool ok)
+DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok)
 {
     // Unwind scope chain to stack depth 0.
-    UnwindScope(cx, frame, 0);
+    ScopeIter si(frame, pc, cx);
+    UnwindScope(cx, si, 0);
 
     // If ScriptDebugEpilogue returns |true| we have to return the frame's
     // return value. If it returns |false|, the debugger threw an exception.
     // In both cases we have to pop debug scopes.
-    ok = ScriptDebugEpilogue(cx, frame, ok);
+    ok = ScriptDebugEpilogue(cx, frame, pc, ok);
 
     if (frame->isNonEvalFunctionFrame()) {
         JS_ASSERT_IF(ok, frame->hasReturnValue());
         DebugScopes::onPopCall(frame, cx);
     } else if (frame->isStrictEvalFrame()) {
         JS_ASSERT_IF(frame->hasCallObj(), frame->scopeChain()->as<CallObject>().isForEval());
         DebugScopes::onPopStrictEvalScope(frame);
     }
@@ -843,17 +844,17 @@ HandleDebugTrap(JSContext *cx, BaselineF
         break;
 
       case JSTRAP_ERROR:
         return false;
 
       case JSTRAP_RETURN:
         *mustReturn = true;
         frame->setReturnValue(rval);
-        return jit::DebugEpilogue(cx, frame, true);
+        return jit::DebugEpilogue(cx, frame, pc, true);
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
@@ -881,17 +882,17 @@ OnDebuggerStatement(JSContext *cx, Basel
         return false;
 
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         frame->setReturnValue(rval);
         *mustReturn = true;
-        return jit::DebugEpilogue(cx, frame, true);
+        return jit::DebugEpilogue(cx, frame, pc, true);
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
@@ -906,22 +907,21 @@ EnterBlock(JSContext *cx, BaselineFrame 
 bool
 LeaveBlock(JSContext *cx, BaselineFrame *frame)
 {
     frame->popBlock(cx);
     return true;
 }
 
 bool
-DebugLeaveBlock(JSContext *cx, BaselineFrame *frame)
+DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
 {
-    JS_ASSERT(frame->hasBlockChain());
+    JS_ASSERT(cx->compartment()->debugMode());
 
-    if (JS_UNLIKELY(cx->compartment()->debugMode()))
-        DebugScopes::onPopBlock(cx, frame);
+    DebugScopes::onPopBlock(cx, frame, pc);
 
     return true;
 }
 
 bool
 InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame, uint32_t numStackValues)
 {
     return frame->initForOsr(interpFrame, numStackValues);
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -633,33 +633,33 @@ bool FilterArgumentsOrEval(JSContext *cx
 
 #ifdef JSGC_GENERATIONAL
 void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
 void PostGlobalWriteBarrier(JSRuntime *rt, JSObject *obj);
 #endif
 
 uint32_t GetIndexFromString(JSString *str);
 
-bool DebugPrologue(JSContext *cx, BaselineFrame *frame, bool *mustReturn);
-bool DebugEpilogue(JSContext *cx, BaselineFrame *frame, bool ok);
+bool DebugPrologue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn);
+bool DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok);
 
 bool StrictEvalPrologue(JSContext *cx, BaselineFrame *frame);
 bool HeavyweightFunPrologue(JSContext *cx, BaselineFrame *frame);
 
 bool NewArgumentsObject(JSContext *cx, BaselineFrame *frame, MutableHandleValue res);
 
 JSObject *InitRestParameter(JSContext *cx, uint32_t length, Value *rest, HandleObject templateObj,
                             HandleObject res);
 
 bool HandleDebugTrap(JSContext *cx, BaselineFrame *frame, uint8_t *retAddr, bool *mustReturn);
 bool OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn);
 
 bool EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
 bool LeaveBlock(JSContext *cx, BaselineFrame *frame);
-bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame);
+bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
 
 bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame,
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset);
 
 } // namespace jit
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5669,19 +5669,18 @@ js_DumpStackFrame(JSContext *cx, StackFr
         fputc('\n', stderr);
 
         fprintf(stderr, "file %s line %u\n",
                 i.script()->filename(), (unsigned) i.script()->lineno);
 
         if (jsbytecode *pc = i.pc()) {
             fprintf(stderr, "  pc = %p\n", pc);
             fprintf(stderr, "  current op: %s\n", js_CodeName[*pc]);
+            MaybeDumpObject("blockChain", i.script()->getBlockScope(pc));
         }
-        if (!i.isJit())
-            MaybeDumpObject("blockChain", i.interpFrame()->maybeBlockChain());
         MaybeDumpValue("this", i.thisv());
         if (!i.isJit()) {
             fprintf(stderr, "  rval: ");
             dumpValue(i.interpFrame()->returnValue());
             fputc('\n', stderr);
         }
 
         fprintf(stderr, "  flags:");
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1686,34 +1686,27 @@ JSAtom *
 ExpressionDecompiler::loadAtom(jsbytecode *pc)
 {
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
 JSAtom *
 ExpressionDecompiler::findLetVar(jsbytecode *pc, unsigned depth)
 {
-    if (script->hasObjects()) {
-        JSObject *chain = script->getBlockScope(pc);
-        if (!chain)
-            return nullptr;
-        JS_ASSERT(chain->is<BlockObject>());
-        do {
-            BlockObject &block = chain->as<BlockObject>();
-            uint32_t blockDepth = block.stackDepth();
-            uint32_t blockCount = block.slotCount();
-            if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
-                for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
-                    const Shape &shape = r.front();
-                    if (shape.shortid() == int(depth - blockDepth))
-                        return JSID_TO_ATOM(shape.propid());
-                }
+    for (JSObject *chain = script->getBlockScope(pc); chain; chain = chain->getParent()) {
+        StaticBlockObject &block = chain->as<StaticBlockObject>();
+        uint32_t blockDepth = block.stackDepth();
+        uint32_t blockCount = block.slotCount();
+        if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
+            for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
+                const Shape &shape = r.front();
+                if (shape.shortid() == int(depth - blockDepth))
+                    return JSID_TO_ATOM(shape.propid());
             }
-            chain = chain->getParent();
-        } while (chain && chain->is<BlockObject>());
+        }
     }
     return nullptr;
 }
 
 JSAtom *
 ExpressionDecompiler::getArg(unsigned slot)
 {
     JS_ASSERT(fun);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2888,24 +2888,26 @@ LazyScript::markChildren(JSTracer *trc)
 
 void
 LazyScript::finalize(FreeOp *fop)
 {
     if (table_)
         fop->free_(table_);
 }
 
-JSObject *
+StaticBlockObject *
 JSScript::getBlockScope(jsbytecode *pc)
 {
     JS_ASSERT(containsPC(pc));
-    JS_ASSERT(pc >= main());
 
     ptrdiff_t offset = pc - main();
 
+    if (offset < 0)
+        return nullptr;
+
     if (!hasBlockScopes())
         return nullptr;
 
     BlockScopeArray *scopeArray = blockScopes();
     JSObject *blockChain = nullptr;
     for (uint32_t n = 0; n < scopeArray->length; n++) {
         const BlockScopeNote *note = &scopeArray->vector[n];
         if (note->start > offset)
@@ -2913,17 +2915,17 @@ JSScript::getBlockScope(jsbytecode *pc)
         if (offset < note->start + note->length) {
             if (note->index == BlockScopeNote::NoBlockScopeIndex)
                 blockChain = nullptr;
             else
                 blockChain = getObject(note->index);
         }
     }
 
-    return blockChain;
+    return blockChain ? &blockChain->as<StaticBlockObject>() : nullptr;
 }
 
 void
 JSScript::setArgumentsHasVarBinding()
 {
     argsHasVarBinding_ = true;
     needsArgsAnalysis_ = true;
 }
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -38,16 +38,17 @@ namespace jit {
 # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript *)0x1)
 
 class BreakpointSite;
 class BindingIter;
 class RegExpObject;
 struct SourceCompressionTask;
 class Shape;
 class WatchpointMap;
+class StaticBlockObject;
 
 namespace analyze {
     class ScriptAnalysis;
 }
 
 namespace frontend {
     class BytecodeEmitter;
 }
@@ -1099,17 +1100,17 @@ class JSScript : public js::gc::Barriere
     inline js::RegExpObject *getRegExp(size_t index);
 
     const js::Value &getConst(size_t index) {
         js::ConstArray *arr = consts();
         JS_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
-    JSObject *getBlockScope(jsbytecode *pc);
+    js::StaticBlockObject *getBlockScope(jsbytecode *pc);
 
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     bool isEmpty() const {
         if (length() > 3)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1507,17 +1507,17 @@ TrapHandler(JSContext *cx, JSScript *, j
 {
     RootedString str(cx, JSVAL_TO_STRING(closure));
     RootedValue rval(cx, *rvalArg);
 
     ScriptFrameIter iter(cx);
     JS_ASSERT(!iter.done());
 
     /* Debug-mode currently disables Ion compilation. */
-    JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr()));
+    JSAbstractFramePtr frame(iter.abstractFramePtr().raw(), iter.pc());
     RootedScript script(cx, iter.script());
 
     size_t length;
     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
     if (!chars)
         return JSTRAP_ERROR;
 
     if (!frame.evaluateUCInStackFrame(cx, chars, length,
@@ -2585,17 +2585,17 @@ EvalInFrame(JSContext *cx, unsigned argc
         ac.construct(cx, DefaultObjectForContextOrNull(cx));
     }
 
     size_t length;
     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
     if (!chars)
         return false;
 
-    JSAbstractFramePtr frame(Jsvalify(fi.abstractFramePtr()));
+    JSAbstractFramePtr frame(fi.abstractFramePtr().raw(), fi.pc());
     RootedScript fpscript(cx, frame.script());
     bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length,
                                              fpscript->filename(),
                                              JS_PCToLineNumber(cx, fpscript,
                                                                fi.pc()),
                                              MutableHandleValue::fromMarkedLocation(vp));
     return ok;
 }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3982,22 +3982,22 @@ DebuggerFrame_getType(JSContext *cx, uns
                           ? cx->names().global
                           : cx->names().call);
     return true;
 }
 
 static bool
 DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
 {
-    THIS_FRAME_OWNER(cx, argc, vp, "get environment", args, thisobj, frame, dbg);
+    THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg);
 
     Rooted<Env*> env(cx);
     {
-        AutoCompartment ac(cx, frame.scopeChain());
-        env = GetDebugScopeForFrame(cx, frame);
+        AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
+        env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
         if (!env)
             return false;
     }
 
     return dbg->wrapEnvironment(cx, env, args.rval());
 }
 
 static bool
@@ -4433,17 +4433,17 @@ DebuggerGenericEval(JSContext *cx, const
 
     RootedValue thisv(cx);
     Rooted<Env *> env(cx);
     if (iter) {
         /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */
         if (!iter->computeThis(cx))
             return false;
         thisv = iter->thisv();
-        env = GetDebugScopeForFrame(cx, iter->abstractFramePtr());
+        env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc());
         if (!env)
             return false;
     } else {
         /*
          * Use the global as 'this'. If the global is an inner object, it
          * should have a thisObject hook that returns the appropriate outer
          * object.
          */
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -840,46 +840,58 @@ EnterWith(JSContext *cx, AbstractFramePt
         return false;
 
     frame.pushOnScopeChain(*withobj);
     return true;
 }
 
 /* Unwind block and scope chains to match the given depth. */
 void
-js::UnwindScope(JSContext *cx, AbstractFramePtr frame, uint32_t stackDepth)
+js::UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth)
 {
-    JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
-    JS_ASSERT_IF(frame.isStackFrame(), stackDepth <= cx->interpreterRegs().stackDepth());
-
-    for (ScopeIter si(frame, cx); !si.done(); ++si) {
+    for (; !si.done(); ++si) {
         switch (si.type()) {
           case ScopeIter::Block:
             if (si.staticBlock().stackDepth() < stackDepth)
                 return;
             if (cx->compartment()->debugMode())
-                DebugScopes::onPopBlock(cx, frame);
-            frame.popBlock(cx);
+                DebugScopes::onPopBlock(cx, si);
+            JS_ASSERT(&si.staticBlock() == si.frame().maybeBlockChain());
+            si.frame().popBlock(cx);
             break;
           case ScopeIter::With:
             if (si.scope().as<WithObject>().stackDepth() < stackDepth)
                 return;
-            frame.popWith(cx);
+            si.frame().popWith(cx);
             break;
           case ScopeIter::Call:
           case ScopeIter::StrictEvalScope:
             break;
         }
     }
 }
 
+static void
+ForcedReturn(JSContext *cx, ScopeIter &si, FrameRegs &regs)
+{
+    UnwindScope(cx, si, 0);
+    regs.setToEndOfScript();
+}
+
+static void
+ForcedReturn(JSContext *cx, FrameRegs &regs)
+{
+    ScopeIter si(regs.fp(), regs.pc, cx);
+    ForcedReturn(cx, si, regs);
+}
+
 void
 js::UnwindForUncatchableException(JSContext *cx, const FrameRegs &regs)
 {
-    /* c.f. the regular (catchable) TryNoteIter loop in Interpret. */
+    /* c.f. the regular (catchable) TryNoteIter loop in HandleError. */
     for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
         JSTryNote *tn = *tni;
         if (tn->kind == JSTRY_ITER) {
             Value *sp = regs.spForStackDepth(tn->stackDepth);
             UnwindIteratorForUncatchableException(cx, &sp[-1].toObject());
         }
     }
 }
@@ -938,16 +950,117 @@ TryNoteIter::settle()
          * depth exceeding the current one and this condition is what we use to
          * filter them out.
          */
         if (tn->stackDepth <= regs.stackDepth())
             break;
     }
 }
 
+enum HandleErrorContinuation
+{
+    SuccessfulReturnContinuation,
+    ErrorReturnContinuation,
+    CatchContinuation,
+    FinallyContinuation
+};
+
+static HandleErrorContinuation
+HandleError(JSContext *cx, FrameRegs &regs)
+{
+    JS_ASSERT(regs.fp()->script()->containsPC(regs.pc));
+
+    ScopeIter si(regs.fp(), regs.pc, cx);
+    bool ok = false;
+
+  again:
+    if (cx->isExceptionPending()) {
+        /* Call debugger throw hooks. */
+        if (JS_UNLIKELY(cx->compartment()->debugMode())) {
+            JSTrapStatus status = DebugExceptionUnwind(cx, regs.fp(), regs.pc);
+            switch (status) {
+              case JSTRAP_ERROR:
+                goto again;
+
+              case JSTRAP_CONTINUE:
+              case JSTRAP_THROW:
+                break;
+
+              case JSTRAP_RETURN:
+                ForcedReturn(cx, si, regs);
+                return SuccessfulReturnContinuation;
+
+              default:
+                MOZ_ASSUME_UNREACHABLE("Invalid trap status");
+            }
+        }
+
+        for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
+            JSTryNote *tn = *tni;
+
+            UnwindScope(cx, si, tn->stackDepth);
+
+            /*
+             * Set pc to the first bytecode after the the try note to point
+             * to the beginning of catch or finally or to [enditer] closing
+             * the for-in loop.
+             */
+            regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
+            regs.sp = regs.spForStackDepth(tn->stackDepth);
+
+            switch (tn->kind) {
+              case JSTRY_CATCH:
+                /* Catch cannot intercept the closing of a generator. */
+                if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
+                    break;
+
+                /*
+                 * Don't clear exceptions to save cx->exception from GC
+                 * until it is pushed to the stack via [exception] in the
+                 * catch block.
+                 */
+                return CatchContinuation;
+
+              case JSTRY_FINALLY:
+                return FinallyContinuation;
+
+              case JSTRY_ITER: {
+                /* This is similar to JSOP_ENDITER in the interpreter loop. */
+                JS_ASSERT(JSOp(*regs.pc) == JSOP_ENDITER);
+                RootedObject obj(cx, &regs.sp[-1].toObject());
+                bool ok = UnwindIteratorForException(cx, obj);
+                regs.sp -= 1;
+                if (!ok)
+                    goto again;
+                break;
+              }
+
+              case JSTRY_LOOP:
+                break;
+            }
+        }
+
+        /*
+         * Propagate the exception or error to the caller unless the exception
+         * is an asynchronous return from a generator.
+         */
+        if (JS_UNLIKELY(cx->isExceptionPending() &&
+                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
+            cx->clearPendingException();
+            ok = true;
+            regs.fp()->clearReturnValue();
+        }
+    } else {
+        UnwindForUncatchableException(cx, regs);
+    }
+
+    ForcedReturn(cx, si, regs);
+    return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation;
+}
+
 #define REGS                     (activation.regs())
 #define PUSH_COPY(v)             do { *REGS.sp++ = (v); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
 #define PUSH_COPY_SKIP_CHECK(v)  *REGS.sp++ = (v)
 #define PUSH_NULL()              REGS.sp++->setNull()
 #define PUSH_UNDEFINED()         REGS.sp++->setUndefined()
 #define PUSH_BOOLEAN(b)          REGS.sp++->setBoolean(b)
 #define PUSH_DOUBLE(d)           REGS.sp++->setDouble(d)
 #define PUSH_INT32(i)            REGS.sp++->setInt32(i)
@@ -1368,35 +1481,34 @@ Interpret(JSContext *cx, RunState &state
 
     if (!activation.entryFrame()->isGeneratorFrame()) {
         if (!activation.entryFrame()->prologue(cx))
             goto error;
     } else {
         probes::EnterScript(cx, script, script->function(), activation.entryFrame());
     }
     if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-        JSTrapStatus status = ScriptDebugPrologue(cx, activation.entryFrame());
+        JSTrapStatus status = ScriptDebugPrologue(cx, activation.entryFrame(), REGS.pc);
         switch (status) {
           case JSTRAP_CONTINUE:
             break;
           case JSTRAP_RETURN:
-            interpReturnOK = true;
-            goto forced_return;
+            ForcedReturn(cx, REGS);
+            goto successful_return_continuation;
           case JSTRAP_THROW:
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
     if (cx->runtime()->profilingScripts || cx->runtime()->debugHooks.interruptHook)
         activation.enableInterruptsUnconditionally();
 
-  enterInterpreterLoop:
     // Enter the interpreter loop starting at the current pc.
     ADVANCE_AND_DISPATCH(0);
 
 INTERPRETER_LOOP() {
 
 CASE(EnableInterruptsPseudoOpcode)
 {
     bool moreInterrupts = false;
@@ -1426,18 +1538,18 @@ CASE(EnableInterruptsPseudoOpcode)
                 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;
+                ForcedReturn(cx, REGS);
+                goto successful_return_continuation;
               case JSTRAP_THROW:
                 cx->setPendingException(rval);
                 goto error;
               default:;
             }
             moreInterrupts = true;
         }
 
@@ -1447,18 +1559,18 @@ CASE(EnableInterruptsPseudoOpcode)
         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;
+                ForcedReturn(cx, REGS);
+                goto successful_return_continuation;
               case JSTRAP_THROW:
                 cx->setPendingException(rval);
                 goto error;
               default:
                 break;
             }
             JS_ASSERT(status == JSTRAP_CONTINUE);
             JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
@@ -1645,26 +1757,28 @@ CASE(JSOP_RETURN)
 CASE(JSOP_RETRVAL)
 {
     /*
      * When the inlined frame exits with an exception or an error, ok will be
      * false after the inline_return label.
      */
     CHECK_BRANCH();
 
+  successful_return_continuation:
     interpReturnOK = true;
+  return_continuation:
     if (activation.entryFrame() != REGS.fp())
   inline_return:
     {
 #if JS_TRACE_LOGGING
         TraceLogging::defaultLogger()->log(TraceLogging::SCRIPT_STOP);
 #endif
 
         if (JS_UNLIKELY(cx->compartment()->debugMode()))
-            interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), interpReturnOK);
+            interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK);
 
         if (!REGS.fp()->isYielding())
             REGS.fp()->epilogue(cx);
         else
             probes::ExitScript(cx, script, script->function(), REGS.fp()->hasPushedSPSFrame());
 
 #if defined(JS_ION)
   jit_return_pop_frame:
@@ -1687,17 +1801,16 @@ CASE(JSOP_RETRVAL)
         }
 
         /* Increment pc so that |sp - fp->slots == ReconstructStackDepth(pc)|. */
         REGS.pc += JSOP_CALL_LENGTH;
         goto error;
     } else {
         JS_ASSERT(REGS.stackDepth() == 0);
     }
-    interpReturnOK = true;
     goto exit;
 }
 
 CASE(JSOP_DEFAULT)
     REGS.sp--;
     /* FALL THROUGH */
 CASE(JSOP_GOTO)
 {
@@ -2563,22 +2676,22 @@ CASE(JSOP_FUNCALL)
 #if JS_TRACE_LOGGING
     TraceLogging::defaultLogger()->log(TraceLogging::SCRIPT_START, script);
     TraceLogging::defaultLogger()->log(TraceLogging::INFO_ENGINE_INTERPRETER);
 #endif
 
     if (!REGS.fp()->prologue(cx))
         goto error;
     if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-        switch (ScriptDebugPrologue(cx, REGS.fp())) {
+        switch (ScriptDebugPrologue(cx, REGS.fp(), REGS.pc)) {
           case JSTRAP_CONTINUE:
             break;
           case JSTRAP_RETURN:
-            interpReturnOK = true;
-            goto forced_return;
+            ForcedReturn(cx, REGS);
+            goto successful_return_continuation;
           case JSTRAP_THROW:
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
@@ -3208,18 +3321,18 @@ CASE(JSOP_DEBUGGER)
         st = Debugger::onDebuggerStatement(cx, &rval);
     switch (st) {
       case JSTRAP_ERROR:
         goto error;
       case JSTRAP_CONTINUE:
         break;
       case JSTRAP_RETURN:
         REGS.fp()->setReturnValue(rval);
-        interpReturnOK = true;
-        goto forced_return;
+        ForcedReturn(cx, REGS);
+        goto successful_return_continuation;
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         goto error;
       default:;
     }
 }
 END_CASE(JSOP_DEBUGGER)
 
@@ -3245,16 +3358,17 @@ CASE(JSOP_ENTERLET2)
 END_CASE(JSOP_ENTERBLOCK)
 
 CASE(JSOP_LEAVEBLOCK)
 CASE(JSOP_LEAVEFORLETIN)
 CASE(JSOP_LEAVEBLOCKEXPR)
 {
     blockDepth = REGS.fp()->blockChain().stackDepth();
 
+    // Pop block from scope chain.
     REGS.fp()->popBlock(cx);
 
     if (*REGS.pc == JSOP_LEAVEBLOCK) {
         /* Pop the block's slots. */
         REGS.sp -= GET_UINT16(REGS.pc);
         JS_ASSERT(REGS.stackDepth() == blockDepth);
     } else if (*REGS.pc == JSOP_LEAVEBLOCKEXPR) {
         /* Pop the block's slots maintaining the topmost expr. */
@@ -3266,23 +3380,23 @@ CASE(JSOP_LEAVEBLOCKEXPR)
         /* Another op will pop; nothing to do here. */
         ADVANCE_AND_DISPATCH(JSOP_LEAVEFORLETIN_LENGTH);
     }
 }
 END_CASE(JSOP_LEAVEBLOCK)
 
 CASE(JSOP_DEBUGLEAVEBLOCK)
 {
-    JS_ASSERT(REGS.fp()->hasBlockChain());
+    JS_ASSERT(script->getBlockScope(REGS.pc));
 
     // FIXME: This opcode should not be necessary.  The debugger shouldn't need
     // help from bytecode to do its job.  See bug 927782.
 
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
-        DebugScopes::onPopBlock(cx, REGS.fp());
+        DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
 }
 END_CASE(JSOP_DEBUGLEAVEBLOCK)
 
 CASE(JSOP_GENERATOR)
 {
     JS_ASSERT(!cx->isExceptionPending());
     REGS.fp()->initGeneratorFrame();
     REGS.pc += JSOP_GENERATOR_LENGTH;
@@ -3334,126 +3448,42 @@ DEFAULT()
     goto error;
 }
 
 } /* interpreter loop */
 
     MOZ_ASSUME_UNREACHABLE("Interpreter loop exited via fallthrough");
 
   error:
-    JS_ASSERT(script->containsPC(REGS.pc));
-
-    if (cx->isExceptionPending()) {
-        /* Call debugger throw hooks. */
-        if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-            JSTrapStatus status = DebugExceptionUnwind(cx, REGS.fp(), REGS.pc);
-            switch (status) {
-              case JSTRAP_ERROR:
-                goto error;
-
-              case JSTRAP_CONTINUE:
-              case JSTRAP_THROW:
-                break;
-
-              case JSTRAP_RETURN:
-                interpReturnOK = true;
-                goto forced_return;
-
-              default:
-                MOZ_ASSUME_UNREACHABLE("Invalid trap status");
-            }
-        }
-
-        for (TryNoteIter tni(cx, REGS); !tni.done(); ++tni) {
-            JSTryNote *tn = *tni;
-
-            UnwindScope(cx, REGS.fp(), tn->stackDepth);
-
-            /*
-             * Set pc to the first bytecode after the the try note to point
-             * to the beginning of catch or finally or to [enditer] closing
-             * the for-in loop.
-             */
-            REGS.pc = (script)->main() + tn->start + tn->length;
-            REGS.sp = REGS.spForStackDepth(tn->stackDepth);
-
-            switch (tn->kind) {
-              case JSTRY_CATCH:
-                /* Catch cannot intercept the closing of a generator. */
-                if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
-                    break;
-
-                /*
-                 * Don't clear exceptions to save cx->exception from GC
-                 * until it is pushed to the stack via [exception] in the
-                 * catch block.
-                 *
-                 * Also, see the comment below about the use of goto here.
-                 */
-                goto enterInterpreterLoop;
-
-              case JSTRY_FINALLY:
-                /*
-                 * Push (true, exception) pair for finally to indicate that
-                 * [retsub] should rethrow the exception.
-                 */
-                PUSH_BOOLEAN(true);
-                PUSH_COPY(cx->getPendingException());
-                cx->clearPendingException();
-
-                /*
-                 * Leave the scope via a plain goto (and not via
-                 * ADVANCE_AND_DISPATCH, which may be implemented with indirect
-                 * goto) so that the TryNoteIter goes out of scope properly.
-                 */
-                goto enterInterpreterLoop;
-
-              case JSTRY_ITER: {
-                /* This is similar to JSOP_ENDITER in the interpreter loop. */
-                JS_ASSERT(JSOp(*REGS.pc) == JSOP_ENDITER);
-                RootedObject &obj = rootObject0;
-                obj = &REGS.sp[-1].toObject();
-                bool ok = UnwindIteratorForException(cx, obj);
-                REGS.sp -= 1;
-                if (!ok)
-                    goto error;
-                break;
-              }
-
-              case JSTRY_LOOP:
-                break;
-           }
-        }
-
+    switch (HandleError(cx, REGS)) {
+      case SuccessfulReturnContinuation:
+        goto successful_return_continuation;
+
+      case ErrorReturnContinuation:
+        interpReturnOK = false;
+        goto return_continuation;
+
+      case CatchContinuation:
+        ADVANCE_AND_DISPATCH(0);
+
+      case FinallyContinuation:
         /*
-         * Propagate the exception or error to the caller unless the exception
-         * is an asynchronous return from a generator.
+         * Push (true, exception) pair for finally to indicate that [retsub]
+         * should rethrow the exception.
          */
-        interpReturnOK = false;
-        if (JS_UNLIKELY(cx->isExceptionPending() &&
-                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
-            cx->clearPendingException();
-            interpReturnOK = true;
-            REGS.fp()->clearReturnValue();
-        }
-    } else {
-        UnwindForUncatchableException(cx, REGS);
-        interpReturnOK = false;
+        PUSH_BOOLEAN(true);
+        PUSH_COPY(cx->getPendingException());
+        cx->clearPendingException();
+        ADVANCE_AND_DISPATCH(0);
     }
-
-  forced_return:
-    UnwindScope(cx, REGS.fp(), 0);
-    REGS.setToEndOfScript();
-
-    if (activation.entryFrame() != REGS.fp())
-        goto inline_return;
+    MOZ_ASSUME_UNREACHABLE("Invalid HandleError continuation");
 
   exit:
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
-        interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), interpReturnOK);
+        interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK);
     if (!REGS.fp()->isYielding())
         REGS.fp()->epilogue(cx);
     else
         probes::ExitScript(cx, script, script->function(), REGS.fp()->hasPushedSPSFrame());
 
     gc::MaybeVerifyBarriers(cx, true);
 
 #if JS_TRACE_LOGGING
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -13,16 +13,18 @@
 
 #include "jsiter.h"
 #include "jspubtd.h"
 
 #include "vm/Stack.h"
 
 namespace js {
 
+class ScopeIter;
+
 /*
  * Announce to the debugger that the thread has entered a new JavaScript frame,
  * |frame|. Call whatever hooks have been registered to observe new frames, and
  * return a JSTrapStatus code indication how execution should proceed:
  *
  * - JSTRAP_CONTINUE: Continue execution normally.
  *
  * - JSTRAP_THROW: Throw an exception. ScriptDebugPrologue has set |cx|'s
@@ -31,17 +33,17 @@ namespace js {
  * - JSTRAP_ERROR: Terminate execution (as is done when a script is terminated
  *   for running too long). ScriptDebugPrologue has cleared |cx|'s pending
  *   exception.
  *
  * - JSTRAP_RETURN: Return from the new frame immediately. ScriptDebugPrologue
  *   has set |frame|'s return value appropriately.
  */
 extern JSTrapStatus
-ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame);
+ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
 
 /*
  * Announce to the debugger that the thread has exited a JavaScript frame, |frame|.
  * If |ok| is true, the frame is returning normally; if |ok| is false, the frame
  * is throwing an exception or terminating.
  *
  * Call whatever hooks have been registered to observe frame exits. Change cx's
  * current exception and |frame|'s return value to reflect the changes in behavior
@@ -49,17 +51,17 @@ ScriptDebugPrologue(JSContext *cx, Abstr
  *
  * This function may be called twice for the same outgoing frame; only the
  * first call has any effect. (Permitting double calls simplifies some
  * cases where an onPop handler's resumption value changes a return to a
  * throw, or vice versa: we can redirect to a complete copy of the
  * alternative path, containing its own call to ScriptDebugEpilogue.)
  */
 extern bool
-ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, bool ok);
+ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc, bool ok);
 
 /*
  * Announce to the debugger that an exception has been thrown and propagated
  * to |frame|. Call whatever hooks have been registered to observe this and
  * return a JSTrapStatus code indication how execution should proceed:
  *
  * - JSTRAP_CONTINUE: Continue throwing the current exception.
  *
@@ -313,17 +315,17 @@ TypeOfObject(JSObject *obj);
 extern JSType
 TypeOfValue(const Value &v);
 
 extern bool
 HasInstance(JSContext *cx, HandleObject obj, HandleValue v, bool *bp);
 
 /* Unwind block and scope chains to match the given depth. */
 extern void
-UnwindScope(JSContext *cx, AbstractFramePtr frame, uint32_t stackDepth);
+UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth);
 
 /*
  * Unwind for an uncatchable exception. This means not running finalizers, etc;
  * just preserving the basic engine stack invariants.
  */
 extern void
 UnwindForUncatchableException(JSContext *cx, const FrameRegs &regs);
 
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -63,28 +63,29 @@ static bool
 IsTopFrameConstructing(JSContext *cx, AbstractFramePtr frame)
 {
     ScriptFrameIter iter(cx);
     JS_ASSERT(iter.abstractFramePtr() == frame);
     return iter.isConstructing();
 }
 
 JSTrapStatus
-js::ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame)
+js::ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
     JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
 
     if (!frame.script()->selfHosted) {
+        JSAbstractFramePtr jsframe(frame.raw(), pc);
         if (frame.isFramePushedByExecute()) {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
-                frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
+                frame.setHookData(hook(cx, jsframe, IsTopFrameConstructing(cx, frame),
                                        true, 0, cx->runtime()->debugHooks.executeHookData));
         } else {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
-                frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
+                frame.setHookData(hook(cx, jsframe, IsTopFrameConstructing(cx, frame),
                                        true, 0, cx->runtime()->debugHooks.callHookData));
         }
     }
 
     RootedValue rval(cx);
     JSTrapStatus status = Debugger::onEnterFrame(cx, frame, &rval);
     switch (status) {
       case JSTRAP_CONTINUE:
@@ -100,30 +101,31 @@ js::ScriptDebugPrologue(JSContext *cx, A
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("bad Debugger::onEnterFrame JSTrapStatus value");
     }
     return status;
 }
 
 bool
-js::ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, bool okArg)
+js::ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc, bool okArg)
 {
     JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
 
     bool ok = okArg;
 
     // We don't add hook data for self-hosted scripts, so we don't need to check for them, here.
     if (void *hookData = frame.maybeHookData()) {
+        JSAbstractFramePtr jsframe(frame.raw(), pc);
         if (frame.isFramePushedByExecute()) {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
-                hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
+                hook(cx, jsframe, IsTopFrameConstructing(cx, frame), false, &ok, hookData);
         } else {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
-                hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
+                hook(cx, jsframe, IsTopFrameConstructing(cx, frame), false, &ok, hookData);
         }
     }
 
     return Debugger::onLeaveFrame(cx, frame, ok);
 }
 
 JSTrapStatus
 js::DebugExceptionUnwind(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
@@ -1223,37 +1225,37 @@ JS::FormatStackDump(JSContext *cx, char 
     }
 
     if (!num)
         buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
 
     return buf;
 }
 
-JSAbstractFramePtr::JSAbstractFramePtr(void *raw)
-  : ptr_(uintptr_t(raw))
+JSAbstractFramePtr::JSAbstractFramePtr(void *raw, jsbytecode *pc)
+  : ptr_(uintptr_t(raw)), pc_(pc)
 { }
 
 JSObject *
 JSAbstractFramePtr::scopeChain(JSContext *cx)
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     RootedObject scopeChain(cx, frame.scopeChain());
     AutoCompartment ac(cx, scopeChain);
-    return GetDebugScopeForFrame(cx, frame);
+    return GetDebugScopeForFrame(cx, frame, pc());
 }
 
 JSObject *
 JSAbstractFramePtr::callObject(JSContext *cx)
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     if (!frame.isFunctionFrame())
         return nullptr;
 
-    JSObject *o = GetDebugScopeForFrame(cx, frame);
+    JSObject *o = GetDebugScopeForFrame(cx, frame, pc());
 
     /*
      * Given that fp is a function frame and GetDebugScopeForFrame always fills
      * in missing scopes, we can expect to find fp's CallObject on 'o'. Note:
      *  - GetDebugScopeForFrame wraps every ScopeObject (missing or not) with
      *    a DebugScopeObject proxy.
      *  - If fp is an eval-in-function, then fp has no callobj of its own and
      *    JS_GetFrameCallObject will return the innermost function's callobj.
@@ -1265,45 +1267,45 @@ JSAbstractFramePtr::callObject(JSContext
         o = o->enclosingScope();
     }
     return nullptr;
 }
 
 JSFunction *
 JSAbstractFramePtr::maybeFun()
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     return frame.maybeFun();
 }
 
 JSScript *
 JSAbstractFramePtr::script()
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     return frame.script();
 }
 
 bool
 JSAbstractFramePtr::getThisValue(JSContext *cx, MutableHandleValue thisv)
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
 
     RootedObject scopeChain(cx, frame.scopeChain());
     js::AutoCompartment ac(cx, scopeChain);
     if (!ComputeThis(cx, frame))
         return false;
 
     thisv.set(frame.thisValue());
     return true;
 }
 
 bool
 JSAbstractFramePtr::isDebuggerFrame()
 {
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     return frame.isDebuggerFrame();
 }
 
 bool
 JSAbstractFramePtr::evaluateInStackFrame(JSContext *cx,
                                          const char *bytes, unsigned length,
                                          const char *filename, unsigned lineno,
                                          MutableHandleValue rval)
@@ -1335,17 +1337,17 @@ JSAbstractFramePtr::evaluateUCInStackFra
     if (!CheckDebugMode(cx))
         return false;
 
     RootedObject scope(cx, scopeChain(cx));
     Rooted<Env*> env(cx, scope);
     if (!env)
         return false;
 
-    AbstractFramePtr frame = Valueify(*this);
+    AbstractFramePtr frame(*this);
     if (!ComputeThis(cx, frame))
         return false;
     RootedValue thisv(cx, frame.thisValue());
 
     js::AutoCompartment ac(cx, env);
     return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
                          filename, lineno, rval);
 }
@@ -1377,17 +1379,17 @@ JSBrokenFrameIterator::operator++()
     *data = iter.data_;
     return *this;
 }
 
 JSAbstractFramePtr
 JSBrokenFrameIterator::abstractFramePtr() const
 {
     NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return Jsvalify(iter.abstractFramePtr());
+    return JSAbstractFramePtr(iter.abstractFramePtr().raw(), iter.pc());
 }
 
 jsbytecode *
 JSBrokenFrameIterator::pc() const
 {
     NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
     return iter.pc();
 }
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -869,52 +869,65 @@ ScopeIter::ScopeIter(JSObject &enclosing
     frame_(NullFramePtr()),
     cur_(cx, &enclosingScope),
     block_(cx, nullptr),
     type_(Type(-1))
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(AbstractFramePtr frame, JSContext *cx
+ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, frame.scopeChain()),
-    block_(cx, frame.maybeBlockChain())
+    block_(cx, frame.script()->getBlockScope(pc))
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx
+ScopeIter::ScopeIter(const ScopeIterKey &key, JSContext *cx
+                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+  : cx(cx),
+    frame_(key.frame()),
+    cur_(cx, key.cur()),
+    block_(cx, key.block()),
+    type_(key.type()),
+    hasScopeObject_(key.hasScopeObject())
+{
+    assertSameCompartment(cx, key.frame());
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+}
+
+ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, ScopeObject &scope, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, &scope),
     block_(cx)
 {
     /*
      * Find the appropriate static block for this iterator, given 'scope'. We
      * know that 'scope' is a (non-optimized) scope on fp's scope chain. We do
      * not, however, know whether fp->maybeScopeChain() encloses 'scope'. E.g.:
      *
      *   let (x = 1) {
      *     g = function() { eval('debugger') };
      *     let (y = 1) g();
      *   }
      *
-     * g will have x's block in its enclosing scope but not y's. However, at
-     * the debugger statement, both the x's and y's blocks will be on
-     * fp->blockChain. Fortunately, we can compare scope object stack depths to
-     * determine the block (if any) that encloses 'scope'.
+     * g will have x's block in its enclosing scope but not y's. However, at the
+     * debugger statement, both the x's and y's blocks will be on the block
+     * chain. Fortunately, we can compare scope object stack depths to determine
+     * the block (if any) that encloses 'scope'.
      */
     if (cur_->is<NestedScopeObject>()) {
-        block_ = frame.maybeBlockChain();
+        block_ = frame.script()->getBlockScope(pc);
         while (block_) {
             if (block_->stackDepth() <= cur_->as<NestedScopeObject>().stackDepth())
                 break;
             block_ = block_->enclosingBlock();
         }
         JS_ASSERT_IF(cur_->is<ClonedBlockObject>(),
                      cur_->as<ClonedBlockObject>().staticBlock() == *block_);
     } else {
@@ -1099,17 +1112,17 @@ class DebugScopeProxy : public BaseProxy
      *
      * handleUnaliasedAccess returns 'true' if the access was unaliased and
      * completed by handleUnaliasedAccess.
      */
     bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope, Handle<ScopeObject*> scope,
                                jsid id, Action action, MutableHandleValue vp)
     {
         JS_ASSERT(&debugScope->scope() == scope);
-        AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(*scope);
+        ScopeIterKey* maybeLiveScope = DebugScopes::hasLiveScope(*scope);
 
         /* Handle unaliased formals, vars, and consts at function scope. */
         if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
             CallObject &callobj = scope->as<CallObject>();
             RootedScript script(cx, callobj.callee().nonLazyScript());
             if (!script->ensureHasTypes(cx))
                 return false;
 
@@ -1120,48 +1133,50 @@ class DebugScopeProxy : public BaseProxy
             if (!bi)
                 return false;
 
             if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) {
                 unsigned i = bi.frameIndex();
                 if (script->varIsAliased(i))
                     return false;
 
-                if (maybeframe) {
+                if (maybeLiveScope) {
+                    AbstractFramePtr frame = maybeLiveScope->frame();
                     if (action == GET)
-                        vp.set(maybeframe.unaliasedVar(i));
+                        vp.set(frame.unaliasedVar(i));
                     else
-                        maybeframe.unaliasedVar(i) = vp;
+                        frame.unaliasedVar(i) = vp;
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
                     else
                         snapshot->setDenseElement(bindings.numArgs() + i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
                     if (action == GET)
                         vp.set(UndefinedValue());
                 }
             } else {
                 JS_ASSERT(bi->kind() == ARGUMENT);
                 unsigned i = bi.frameIndex();
                 if (script->formalIsAliased(i))
                     return false;
 
-                if (maybeframe) {
-                    if (script->argsObjAliasesFormals() && maybeframe.hasArgsObj()) {
+                if (maybeLiveScope) {
+                    AbstractFramePtr frame = maybeLiveScope->frame();
+                    if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
                         if (action == GET)
-                            vp.set(maybeframe.argsObj().arg(i));
+                            vp.set(frame.argsObj().arg(i));
                         else
-                            maybeframe.argsObj().setArg(i, vp);
+                            frame.argsObj().setArg(i, vp);
                     } else {
                         if (action == GET)
-                            vp.set(maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING));
+                            vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING));
                         else
-                            maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
+                            frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
                     }
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(i));
                     else
                         snapshot->setDenseElement(i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
@@ -1182,23 +1197,24 @@ class DebugScopeProxy : public BaseProxy
             Shape *shape = block->lastProperty()->search(cx, id);
             if (!shape)
                 return false;
 
             unsigned i = shape->shortid();
             if (block->staticBlock().isAliased(i))
                 return false;
 
-            if (maybeframe) {
-                JSScript *script = maybeframe.script();
+            if (maybeLiveScope) {
+                AbstractFramePtr frame = maybeLiveScope->frame();
+                JSScript *script = frame.script();
                 unsigned local = block->slotToLocalIndex(script->bindings, shape->slot());
                 if (action == GET)
-                    vp.set(maybeframe.unaliasedLocal(local));
+                    vp.set(frame.unaliasedLocal(local));
                 else
-                    maybeframe.unaliasedLocal(local) = vp;
+                    frame.unaliasedLocal(local) = vp;
                 JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script));
             } else {
                 if (action == GET)
                     vp.set(block->var(i, DONT_CHECK_ALIASING));
                 else
                     block->setVar(i, vp, DONT_CHECK_ALIASING);
             }
 
@@ -1245,24 +1261,24 @@ class DebugScopeProxy : public BaseProxy
         *maybeArgsObj = nullptr;
 
         if (!isArguments(cx, id) || !isFunctionScope(scope))
             return true;
 
         if (scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj())
             return true;
 
-        AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(scope);
-        if (!maybeframe) {
+        ScopeIterKey *maybeScope = DebugScopes::hasLiveScope(scope);
+        if (!maybeScope) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                  "Debugger scope");
             return false;
         }
 
-        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybeframe);
+        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybeScope->frame());
         return true;
     }
 
   public:
     static int family;
     static DebugScopeProxy singleton;
 
     DebugScopeProxy() : BaseProxyHandler(&family) {}
@@ -1694,17 +1710,17 @@ DebugScopes::addDebugScope(JSContext *cx
 
     JS_ASSERT(!scopes->missingScopes.has(si));
     if (!scopes->missingScopes.put(si, &debugScope)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
-    if (!scopes->liveScopes.put(&debugScope.scope(), si.frame())) {
+    if (!scopes->liveScopes.put(&debugScope.scope(), si)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope());
 
     return true;
 }
 
@@ -1728,17 +1744,17 @@ DebugScopes::onPopCall(AbstractFramePtr 
         if (!frame.hasCallObj())
             return;
 
         CallObject &callobj = frame.scopeChain()->as<CallObject>();
         scopes->liveScopes.remove(&callobj);
         if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj))
             debugScope = &p->value()->as<DebugScopeObject>();
     } else {
-        ScopeIter si(frame, cx);
+        ScopeIter si(frame, frame.script()->main(), cx);
         if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) {
             debugScope = p->value();
             scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
             scopes->missingScopes.remove(p);
         }
     }
 
     /*
@@ -1783,34 +1799,45 @@ DebugScopes::onPopCall(AbstractFramePtr 
             return;
         }
 
         debugScope->initSnapshot(*snapshot);
     }
 }
 
 void
-DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame)
+DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
     assertSameCompartment(cx, frame);
 
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
-    StaticBlockObject &staticBlock = *frame.maybeBlockChain();
-    if (staticBlock.needsClone()) {
-        ClonedBlockObject &clone = frame.scopeChain()->as<ClonedBlockObject>();
-        clone.copyUnaliasedValues(frame);
+    ScopeIter si(frame, pc, cx);
+    onPopBlock(cx, si);
+}
+
+void
+DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si)
+{
+    DebugScopes *scopes = cx->compartment()->debugScopes;
+    if (!scopes)
+        return;
+
+    JS_ASSERT(si.type() == ScopeIter::Block);
+
+    if (si.staticBlock().needsClone()) {
+        ClonedBlockObject &clone = si.scope().as<ClonedBlockObject>();
+        clone.copyUnaliasedValues(si.frame());
         scopes->liveScopes.remove(&clone);
     } else {
-        ScopeIter si(frame, cx);
         if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) {
             ClonedBlockObject &clone = p->value()->scope().as<ClonedBlockObject>();
-            clone.copyUnaliasedValues(frame);
+            clone.copyUnaliasedValues(si.frame());
             scopes->liveScopes.remove(&clone);
             scopes->missingScopes.remove(p);
         }
     }
 }
 
 void
 DebugScopes::onPopWith(AbstractFramePtr frame)
@@ -1872,48 +1899,48 @@ DebugScopes::updateLiveScopes(JSContext 
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame.scopeChain()->compartment() != cx->compartment())
             continue;
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
-        for (ScopeIter si(frame, cx); !si.done(); ++si) {
+        for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) {
             if (si.hasScopeObject()) {
                 JS_ASSERT(si.scope().compartment() == cx->compartment());
                 DebugScopes *scopes = ensureCompartmentData(cx);
                 if (!scopes)
                     return false;
-                if (!scopes->liveScopes.put(&si.scope(), frame))
+                if (!scopes->liveScopes.put(&si.scope(), si))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
         }
 
         if (frame.prevUpToDate())
             return true;
         JS_ASSERT(frame.scopeChain()->compartment()->debugMode());
         frame.setPrevUpToDate();
     }
 
     return true;
 }
 
-AbstractFramePtr
-DebugScopes::hasLiveFrame(ScopeObject &scope)
+ScopeIterKey*
+DebugScopes::hasLiveScope(ScopeObject &scope)
 {
     DebugScopes *scopes = scope.compartment()->debugScopes;
     if (!scopes)
-        return NullFramePtr();
+        return nullptr;
 
     if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
-        return p->value();
+        return &p->value();
 
-    return NullFramePtr();
+    return nullptr;
 }
 
 /*****************************************************************************/
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si);
 
 static DebugScopeObject *
@@ -2024,18 +2051,18 @@ GetDebugScope(JSContext *cx, JSObject &o
         JSObject *o = &obj;
         while ((o = o->enclosingScope()))
             JS_ASSERT(!o->is<ScopeObject>());
 #endif
         return &obj;
     }
 
     Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>());
-    if (AbstractFramePtr frame = DebugScopes::hasLiveFrame(*scope)) {
-        ScopeIter si(frame, *scope, cx);
+    if (ScopeIterKey *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) {
+        ScopeIter si(*maybeLiveScope, cx);
         return GetDebugScope(cx, si);
     }
     ScopeIter si(scope->enclosingScope(), cx);
     return GetDebugScopeForScope(cx, scope, si);
 }
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si)
@@ -2060,22 +2087,22 @@ js::GetDebugScopeForFunction(JSContext *
     assertSameCompartment(cx, fun);
     JS_ASSERT(cx->compartment()->debugMode());
     if (!DebugScopes::updateLiveScopes(cx))
         return nullptr;
     return GetDebugScope(cx, *fun->environment());
 }
 
 JSObject *
-js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame)
+js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
     assertSameCompartment(cx, frame);
     if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
         return nullptr;
-    ScopeIter si(frame, cx);
+    ScopeIter si(frame, pc, cx);
     return GetDebugScope(cx, si);
 }
 
 #ifdef DEBUG
 
 typedef HashSet<PropertyName *> PropertyNameSet;
 
 static bool
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -529,32 +529,35 @@ class ScopeIter
     /* ScopeIter does not have value semantics. */
     ScopeIter(const ScopeIter &si) MOZ_DELETE;
 
     ScopeIter(JSContext *cx) MOZ_DELETE;
 
   public:
 
     /* Constructing from a copy of an existing ScopeIter. */
-    explicit ScopeIter(const ScopeIter &si, JSContext *cx
-                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    ScopeIter(const ScopeIter &si, JSContext *cx
+              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /* Constructing from StackFrame places ScopeIter on the innermost scope. */
-    explicit ScopeIter(AbstractFramePtr frame, JSContext *cx
-                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
+              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /*
      * Without a StackFrame, the resulting ScopeIter is done() with
      * enclosingScope() as given.
      */
-    explicit ScopeIter(JSObject &enclosingScope, JSContext *cx
-                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    ScopeIter(JSObject &enclosingScope, JSContext *cx
+              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /* Like ScopeIter(StackFrame *) except start at 'scope'. */
-    ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx
+    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, ScopeObject &scope, JSContext *cx
+              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+
+    ScopeIter(const ScopeIterKey &key, JSContext *cx
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     bool done() const { return !frame_; }
 
     /* If done(): */
 
     JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
 
@@ -573,25 +576,30 @@ class ScopeIter
 };
 
 class ScopeIterKey
 {
     AbstractFramePtr frame_;
     JSObject *cur_;
     StaticBlockObject *block_;
     ScopeIter::Type type_;
+    bool hasScopeObject_;
 
   public:
     ScopeIterKey() : frame_(NullFramePtr()), cur_(nullptr), block_(nullptr), type_() {}
     ScopeIterKey(const ScopeIter &si)
-      : frame_(si.frame_), cur_(si.cur_), block_(si.block_), type_(si.type_)
+      : frame_(si.frame_), cur_(si.cur_), block_(si.block_), type_(si.type_),
+        hasScopeObject_(si.hasScopeObject_)
     {}
 
     AbstractFramePtr frame() const { return frame_; }
+    JSObject *cur() const { return cur_; }
+    StaticBlockObject *block() const { return block_; }
     ScopeIter::Type type() const { return type_; }
+    bool hasScopeObject() const { return hasScopeObject_; }
 
     /* For use as hash policy */
     typedef ScopeIterKey Lookup;
     static HashNumber hash(ScopeIterKey si);
     static bool match(ScopeIterKey si1, ScopeIterKey si2);
 };
 
 /*****************************************************************************/
@@ -620,17 +628,17 @@ class ScopeIterKey
  * always produced for the same underlying scope (optimized or not!). This is
  * maintained by some bookkeeping information stored in DebugScopes.
  */
 
 extern JSObject *
 GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
 
 extern JSObject *
-GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame);
+GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
 
 /* Provides debugger access to a scope. */
 class DebugScopeObject : public ProxyObject
 {
     /*
      * The enclosing scope on the dynamic scope chain. This slot is analogous
      * to the SCOPE_CHAIN_SLOT of a ScopeObject.
      */
@@ -678,17 +686,17 @@ class DebugScopes
     /*
      * The map from scope objects of live frames to the live frame. This map
      * updated lazily whenever the debugger needs the information. In between
      * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
      * removes scopes as they are popped). Thus, two consecutive debugger lazy
      * updates of liveScopes need only fill in the new scopes.
      */
     typedef HashMap<ScopeObject *,
-                    AbstractFramePtr,
+                    ScopeIterKey,
                     DefaultHasher<ScopeObject *>,
                     RuntimeAllocPolicy> LiveScopeMap;
     LiveScopeMap liveScopes;
     static JS_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map,
                                                             ScopeObject *key);
 
   public:
     DebugScopes(JSContext *c);
@@ -705,22 +713,23 @@ class DebugScopes
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
     static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
     static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
 
     static bool updateLiveScopes(JSContext *cx);
-    static AbstractFramePtr hasLiveFrame(ScopeObject &scope);
+    static ScopeIterKey *hasLiveScope(ScopeObject &scope);
 
     // In debug-mode, these must be called whenever exiting a scope that might
     // have stack-allocated locals.
     static void onPopCall(AbstractFramePtr frame, JSContext *cx);
-    static void onPopBlock(JSContext *cx, AbstractFramePtr frame);
+    static void onPopBlock(JSContext *cx, const ScopeIter &si);
+    static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
     static void onPopWith(AbstractFramePtr frame);
     static void onPopStrictEvalScope(AbstractFramePtr frame);
     static void onCompartmentLeaveDebugMode(JSCompartment *c);
 };
 
 }  /* namespace js */
 
 template<>
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -990,19 +990,16 @@ InitialFrameFlagsFromConstructing(bool b
 }
 
 static inline bool
 InitialFrameFlagsAreConstructing(InitialFrameFlags initial)
 {
     return !!(initial & INITIAL_CONSTRUCT);
 }
 
-inline AbstractFramePtr Valueify(JSAbstractFramePtr frame) { return AbstractFramePtr(frame); }
-static inline JSAbstractFramePtr Jsvalify(AbstractFramePtr frame)   { return JSAbstractFramePtr(frame.raw()); }
-
 /*****************************************************************************/
 
 class FrameRegs
 {
   public:
     Value *sp;
     jsbytecode *pc;
   private: