Bug 910782 - SpiderMonkey: Implement indirect-goto-based opcode dispatch. r=luke
authorDan Gohman <sunfish@google.com>
Mon, 28 Oct 2013 10:32:50 -0700
changeset 152533 fb2bf717ab2469524a68194e5982fa9ef4913d56
parent 152532 af637e56ce1226bc16c72e202f3eebd74953b062
child 152534 953dc75f2bb9b45e248299a4ea86c4b5e945b81e
push id25551
push userphilringnalda@gmail.com
push dateMon, 28 Oct 2013 23:57:55 +0000
treeherdermozilla-central@518f5bff0ae4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs910782
milestone27.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 910782 - SpiderMonkey: Implement indirect-goto-based opcode dispatch. r=luke
js/src/jsopcode.tbl
js/src/vm/Interpreter.cpp
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -512,8 +512,42 @@ OPDEF(JSOP_IMPLICITTHIS,  226, "implicit
  * This opcode is the target of the entry jump for some loop. The uint8 argument
  * is the loop depth. This value starts at 1 and is just a hint: deeply
  * nested loops all have the same value.
  */
 OPDEF(JSOP_LOOPENTRY,     227, "loopentry",    NULL,  2,  0,  0,  JOF_UINT8)
 
 /* Notes the point at which a value is pushed as an argument. */
 OPDEF(JSOP_NOTEARG,       228, "notearg",      NULL,  1,  0,  0,  JOF_BYTE)
+
+/*
+ * Pad out the unused opcode space to the nearest power-of-two boundary. The
+ * interpreter uses this to construct a table which is a power-of-two size.
+ */
+#ifdef OPPAD
+OPPAD(229)
+OPPAD(230)
+OPPAD(231)
+OPPAD(232)
+OPPAD(233)
+OPPAD(234)
+OPPAD(235)
+OPPAD(236)
+OPPAD(237)
+OPPAD(238)
+OPPAD(239)
+OPPAD(240)
+OPPAD(241)
+OPPAD(242)
+OPPAD(243)
+OPPAD(244)
+OPPAD(245)
+OPPAD(246)
+OPPAD(247)
+OPPAD(248)
+OPPAD(249)
+OPPAD(250)
+OPPAD(251)
+OPPAD(252)
+OPPAD(253)
+OPPAD(254)
+OPPAD(255)
+#endif
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1212,80 +1212,135 @@ Interpret(JSContext *cx, RunState &state
 {
     JSAutoResolveFlags rf(cx, RESOLVE_INFER);
 
     gc::MaybeVerifyBarriers(cx, true);
 
     JS_ASSERT(!cx->compartment()->activeAnalysis);
 
 #define CHECK_PCCOUNT_INTERRUPTS() \
-    JS_ASSERT_IF(script->hasScriptCounts, switchMask == EnableInterruptsPseudoOpcode)
+    JS_ASSERT_IF(script->hasScriptCounts, opMask == EnableInterruptsPseudoOpcode)
 
     /*
      * When Debugger puts a script in single-step mode, all js::Interpret
      * invocations that might be presently running that script must have
      * interrupts enabled. It's not practical to simply check
      * script->stepModeEnabled() at each point some callee could have changed
      * it, because there are so many places js::Interpret could possibly cause
      * JavaScript to run: each place an object might be coerced to a primitive
      * or a number, for example. So instead, we expose a simple mechanism to
      * let Debugger tweak the affected js::Interpret frames when an onStep
-     * handler is added: setting switchMask to EnableInterruptsPseudoOpcode
-     * will enable interrupts.
+     * handler is added: setting opMask to EnableInterruptsPseudoOpcode will
+     * enable interrupts.
      */
     static_assert(EnableInterruptsPseudoOpcode >= JSOP_LIMIT,
                   "EnableInterruptsPseudoOpcode must be greater than any opcode");
     static_assert(EnableInterruptsPseudoOpcode == jsbytecode(-1),
                   "EnableInterruptsPseudoOpcode must be the maximum jsbytecode value");
-    jsbytecode switchMask = 0;
+    jsbytecode opMask = 0;
+
+/*
+ * Define macros for an interpreter loop. Opcode dispatch may be either by a
+ * switch statement or by indirect goto (aka a threaded interpreter), depending
+ * on compiler support.
+ *
+ * Threaded interpretation appears to be well-supported by GCC 3 and higher.
+ * IBM's C compiler when run with the right options (e.g., -qlanglvl=extended)
+ * also supports threading. Ditto the SunPro C compiler.
+ */
+#if (__GNUC__ >= 3 ||                                                         \
+     (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) ||                      \
+     __SUNPRO_C >= 0x570)
+// Non-standard but faster indirect-goto-based dispatch.
+# define INTERPRETER_LOOP()
+# define CASE(OP)                 label_##OP:
+# define DEFAULT()                label_default:
+# define DISPATCH_TO(OP)          goto *(LABEL(JSOP_NOP) + offsets[(OP)])
+
+# define LABEL(X)                 ((const char *)&&label_##X)
+# define LABEL_OFFSET(X)          (int32_t((X) - LABEL(JSOP_NOP)))
+
+    // We use offsets instead of absolute addresses to avoid PIC load-time
+    // relocations. See the remark about shared libraries here for more info:
+    // http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
+    static const int32_t offsets[EnableInterruptsPseudoOpcode + 1] = {
+# define OPDEF(op,v,n,t,l,u,d,f)  LABEL_OFFSET(LABEL(op)),
+# define OPPAD(v)                                                             \
+    LABEL_OFFSET((v) == EnableInterruptsPseudoOpcode ?                        \
+                 LABEL(EnableInterruptsPseudoOpcode) :                        \
+                 LABEL(default)),
+# include "jsopcode.tbl"
+# undef OPDEF
+# undef OPPAD
+    };
+#else
+// Portable switch-based dispatch.
+# define INTERPRETER_LOOP()       the_switch: switch (switchOp)
+# define CASE(OP)                 case OP:
+# define DEFAULT()                default:
+# define DISPATCH_TO(OP)                                                      \
+    JS_BEGIN_MACRO                                                            \
+        switchOp = (OP);                                                      \
+        goto the_switch;                                                      \
+    JS_END_MACRO
+
+    // This variable is effectively a parameter to the_switch.
     jsbytecode switchOp;
-
-#define ADVANCE_AND_DO_OP() goto advanceAndDoOp
-#define DO_OP()            goto do_op
-#define DO_SWITCH()        goto do_switch
-
-#define CASE(OP)           case OP:
-#define END_CASE(OP)       ADVANCE_AND_DISPATCH(OP##_LENGTH);
-
-#define END_VARLEN_CASE    ADVANCE_AND_DO_OP();
-
+#endif
+
+#define END_CASE(OP)              ADVANCE_AND_DISPATCH(OP##_LENGTH);
+
+    /*
+     * Increment regs.pc by N, load the opcode at that position, and jump to the
+     * code to execute it.
+     */
 #define ADVANCE_AND_DISPATCH(N)                                               \
     JS_BEGIN_MACRO                                                            \
-        len = (N);                                                            \
-        ADVANCE_AND_DO_OP();                                                  \
-     JS_END_MACRO
-
-#define LOAD_DOUBLE(PCOFF, dbl)                                               \
-    ((dbl) = script->getConst(GET_UINT32_INDEX(regs.pc + (PCOFF))).toDouble())
+        regs.pc += (N);                                                       \
+        SANITY_CHECKS();                                                      \
+        DISPATCH_TO(*regs.pc | opMask);                                       \
+    JS_END_MACRO
 
     /*
      * Prepare to call a user-supplied branch handler, and abort the script
      * if it returns false.
      */
 #define CHECK_BRANCH()                                                        \
     JS_BEGIN_MACRO                                                            \
         if (cx->runtime()->interrupt && !js_HandleExecutionInterrupt(cx))     \
             goto error;                                                       \
     JS_END_MACRO
 
+    /*
+     * This is a simple wrapper around ADVANCE_AND_DISPATCH which also does
+     * a CHECK_BRANCH() if n is not positive, which possibly indicates that it
+     * is the backedge of a loop.
+     */
 #define BRANCH(n)                                                             \
     JS_BEGIN_MACRO                                                            \
         int32_t nlen = (n);                                                   \
-        regs.pc += nlen;                                                      \
-        op = (JSOp) *regs.pc;                                                 \
         if (nlen <= 0)                                                        \
             CHECK_BRANCH();                                                   \
-        DO_OP();                                                              \
+        ADVANCE_AND_DISPATCH(nlen);                                           \
     JS_END_MACRO
 
+#define LOAD_DOUBLE(PCOFF, dbl)                                               \
+    ((dbl) = script->getConst(GET_UINT32_INDEX(regs.pc + (PCOFF))).toDouble())
+
 #define SET_SCRIPT(s)                                                         \
     JS_BEGIN_MACRO                                                            \
         script = (s);                                                         \
         if (script->hasAnyBreakpointsOrStepMode() || script->hasScriptCounts) \
-            switchMask = EnableInterruptsPseudoOpcode; /* Enable interrupts. */ \
+            opMask = EnableInterruptsPseudoOpcode; /* Enable interrupts. */   \
+    JS_END_MACRO
+
+#define SANITY_CHECKS()                                                       \
+    JS_BEGIN_MACRO                                                            \
+        js::gc::MaybeVerifyBarriers(cx);                                      \
+        CHECK_PCCOUNT_INTERRUPTS();                                           \
     JS_END_MACRO
 
     FrameRegs regs;
     FrameGuard fg(state, regs);
 
     StackFrame *entryFrame = state.pushInterpreterFrame(cx, &fg);
     if (!entryFrame)
         return false;
@@ -1294,17 +1349,17 @@ Interpret(JSContext *cx, RunState &state
         regs.prepareToRun(*entryFrame, state.script());
         JS_ASSERT(regs.pc == state.script()->code);
     } else {
         regs = state.asGenerator()->gen()->regs;
     }
 
     JS_ASSERT_IF(entryFrame->isEvalFrame(), state.script()->isActiveEval);
 
-    InterpreterActivation activation(cx, entryFrame, regs, &switchMask);
+    InterpreterActivation activation(cx, entryFrame, regs, &opMask);
 
     /* The script is used frequently, so keep a local copy. */
     RootedScript script(cx);
     SET_SCRIPT(regs.fp()->script());
 
 #if JS_TRACE_LOGGING
     TraceLogging::defaultLogger()->log(TraceLogging::SCRIPT_START, script);
     TraceLogging::defaultLogger()->log(TraceLogging::INFO_ENGINE_INTERPRETER);
@@ -1362,43 +1417,29 @@ Interpret(JSContext *cx, RunState &state
           case JSTRAP_THROW:
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
-    /*
-     * It is important that "op" be initialized before calling DO_OP because
-     * it is possible for "op" to be specially assigned during the normal
-     * processing of an opcode while looping. We rely on |ADVANCE_AND_DO_OP()|
-     * to manage "op" correctly in all other cases.
-     */
-    JSOp op;
-    int32_t len;
-    len = 0;
-
     if (cx->runtime()->profilingScripts || cx->runtime()->debugHooks.interruptHook)
-        switchMask = EnableInterruptsPseudoOpcode; /* Enable interrupts. */
-
-  advanceAndDoOp:
-    js::gc::MaybeVerifyBarriers(cx);
-    regs.pc += len;
-    op = (JSOp) *regs.pc;
-
-  do_op:
-    CHECK_PCCOUNT_INTERRUPTS();
-    switchOp = jsbytecode(op) | switchMask;
-  do_switch:
-    switch (switchOp) {
+        opMask = EnableInterruptsPseudoOpcode; /* Enable interrupts. */
+
+  enterInterpreterLoop:
+    // Enter the interpreter loop starting at the current pc.
+    ADVANCE_AND_DISPATCH(0);
+
+INTERPRETER_LOOP() {
 
 CASE(EnableInterruptsPseudoOpcode)
 {
     bool moreInterrupts = false;
+    jsbytecode op = *regs.pc;
 
     if (cx->runtime()->profilingScripts) {
         if (!script->hasScriptCounts)
             script->initScriptCounts(cx);
         moreInterrupts = true;
     }
 
     if (script->hasScriptCounts) {
@@ -1450,21 +1491,22 @@ CASE(EnableInterruptsPseudoOpcode)
             goto error;
           default:
             break;
         }
         JS_ASSERT(status == JSTRAP_CONTINUE);
         JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
     }
 
-    JS_ASSERT(switchMask == EnableInterruptsPseudoOpcode);
-    switchMask = moreInterrupts ? EnableInterruptsPseudoOpcode : 0;
-
-    switchOp = jsbytecode(op);
-    DO_SWITCH();
+    JS_ASSERT(opMask == EnableInterruptsPseudoOpcode);
+    opMask = moreInterrupts ? EnableInterruptsPseudoOpcode : 0;
+
+    /* Commence executing the actual opcode. */
+    SANITY_CHECKS();
+    DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_UNUSED44)
 CASE(JSOP_UNUSED45)
 CASE(JSOP_UNUSED46)
 CASE(JSOP_UNUSED47)
@@ -1530,17 +1572,17 @@ CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED219)
 CASE(JSOP_UNUSED220)
 CASE(JSOP_UNUSED221)
 CASE(JSOP_UNUSED222)
 CASE(JSOP_UNUSED223)
 CASE(JSOP_CONDSWITCH)
 CASE(JSOP_TRY)
 {
-    JS_ASSERT(js_CodeSpec[op].length == 1);
+    JS_ASSERT(js_CodeSpec[*regs.pc].length == 1);
     ADVANCE_AND_DISPATCH(1);
 }
 
 CASE(JSOP_LOOPHEAD)
 END_CASE(JSOP_LOOPHEAD)
 
 CASE(JSOP_LABEL)
 END_CASE(JSOP_LABEL)
@@ -1730,17 +1772,17 @@ END_CASE(JSOP_AND)
 #define FETCH_ELEMENT_ID(n, id)                                               \
     JS_BEGIN_MACRO                                                            \
         if (!ValueToId<CanGC>(cx, regs.stackHandleAt(n), &(id)))              \
             goto error;                                                       \
     JS_END_MACRO
 
 #define TRY_BRANCH_AFTER_COND(cond,spdec)                                     \
     JS_BEGIN_MACRO                                                            \
-        JS_ASSERT(js_CodeSpec[op].length == 1);                               \
+        JS_ASSERT(js_CodeSpec[*regs.pc].length == 1);                         \
         unsigned diff_ = (unsigned) GET_UINT8(regs.pc) - (unsigned) JSOP_IFEQ;\
         if (diff_ <= 1) {                                                     \
             regs.sp -= (spdec);                                               \
             if ((cond) == (diff_ != 0)) {                                     \
                 ++regs.pc;                                                    \
                 BRANCH(GET_JUMP_OFFSET(regs.pc));                             \
             }                                                                 \
             ADVANCE_AND_DISPATCH(1 + JSOP_IFEQ_LENGTH);                       \
@@ -2327,17 +2369,17 @@ CASE(JSOP_CALLELEM)
     HandleValue rval = regs.stackHandleAt(-1);
     MutableHandleValue res = regs.stackHandleAt(-2);
 
     bool done = false;
     if (!GetElemOptimizedArguments(cx, regs.fp(), lval, rval, res, &done))
         goto error;
 
     if (!done) {
-        if (!GetElementOperation(cx, op, lval, rval, res))
+        if (!GetElementOperation(cx, JSOp(*regs.pc), lval, rval, res))
             goto error;
     }
 
     TypeScript::Monitor(cx, script, regs.pc, res);
     regs.sp--;
 }
 END_CASE(JSOP_GETELEM)
 
@@ -2397,47 +2439,52 @@ CASE(JSOP_SPREADEVAL)
     JS_ASSERT(regs.stackDepth() >= 3);
     RootedObject &aobj = rootObject0;
     aobj = &regs.sp[-1].toObject();
 
     uint32_t length = aobj->as<ArrayObject>().length();
 
     if (length > ARGS_LENGTH_MAX) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             op == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS
-                                                  : JSMSG_TOO_MANY_FUN_SPREADARGS);
+                             *regs.pc == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS
+                                                        : JSMSG_TOO_MANY_FUN_SPREADARGS);
         goto error;
     }
 
     InvokeArgs args(cx);
 
     if (!args.init(length))
         return false;
 
     args.setCallee(regs.sp[-3]);
     args.setThis(regs.sp[-2]);
 
     if (!GetElements(cx, aobj, length, args.array()))
         goto error;
 
-    if (op == JSOP_SPREADNEW) {
+    switch (*regs.pc) {
+      case JSOP_SPREADNEW:
         if (!InvokeConstructor(cx, args))
             goto error;
-    } else if (op == JSOP_SPREADCALL) {
+        break;
+      case JSOP_SPREADCALL:
         if (!Invoke(cx, args))
             goto error;
-    } else {
-        JS_ASSERT(op == JSOP_SPREADEVAL);
+        break;
+      case JSOP_SPREADEVAL:
         if (IsBuiltinEvalForScope(regs.fp()->scopeChain(), args.calleev())) {
             if (!DirectEval(cx, args))
                 goto error;
         } else {
             if (!Invoke(cx, args))
                 goto error;
         }
+        break;
+      default:
+        MOZ_ASSUME_UNREACHABLE("bad spread opcode");
     }
 
     regs.sp -= 2;
     regs.sp[-1] = args.rval();
     TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
 }
 END_CASE(JSOP_SPREADCALL)
 
@@ -2562,18 +2609,17 @@ CASE(JSOP_FUNCALL)
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
     /* Load first op and dispatch it (safe since JSOP_STOP). */
-    op = (JSOp) *regs.pc;
-    DO_OP();
+    ADVANCE_AND_DISPATCH(0);
 }
 
 CASE(JSOP_SETCALL)
 {
     JS_ALWAYS_FALSE(SetCallOperation(cx));
     goto error;
 }
 END_CASE(JSOP_SETCALL)
@@ -2692,48 +2738,48 @@ END_CASE(JSOP_FALSE)
 
 CASE(JSOP_TRUE)
     PUSH_BOOLEAN(true);
 END_CASE(JSOP_TRUE)
 
 CASE(JSOP_TABLESWITCH)
 {
     jsbytecode *pc2 = regs.pc;
-    len = GET_JUMP_OFFSET(pc2);
+    int32_t len = GET_JUMP_OFFSET(pc2);
 
     /*
      * ECMAv2+ forbids conversion of discriminant, so we will skip to the
      * default case if the discriminant isn't already an int jsval.  (This
      * opcode is emitted only for dense int-domain switches.)
      */
     const Value &rref = *--regs.sp;
     int32_t i;
     if (rref.isInt32()) {
         i = rref.toInt32();
     } else {
         double d;
         /* Don't use mozilla::DoubleIsInt32; treat -0 (double) as 0. */
         if (!rref.isDouble() || (d = rref.toDouble()) != (i = int32_t(rref.toDouble())))
-            ADVANCE_AND_DO_OP();
+            ADVANCE_AND_DISPATCH(len);
     }
 
     pc2 += JUMP_OFFSET_LEN;
     int32_t low = GET_JUMP_OFFSET(pc2);
     pc2 += JUMP_OFFSET_LEN;
     int32_t high = GET_JUMP_OFFSET(pc2);
 
     i -= low;
     if ((uint32_t)i < (uint32_t)(high - low + 1)) {
         pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i;
         int32_t off = (int32_t) GET_JUMP_OFFSET(pc2);
         if (off)
             len = off;
     }
+    ADVANCE_AND_DISPATCH(len);
 }
-END_VARLEN_CASE
 
 CASE(JSOP_ARGUMENTS)
     JS_ASSERT(!regs.fp()->fun()->hasRest());
     if (!script->analyzedArgsUsage() && !script->ensureRanAnalysis(cx))
         goto error;
     if (script->needsArgsObj()) {
         ArgumentsObject *obj = ArgumentsObject::createExpected(cx, regs.fp());
         if (!obj)
@@ -2831,17 +2877,17 @@ END_CASE(JSOP_SETLOCAL)
 
 CASE(JSOP_DEFCONST)
 CASE(JSOP_DEFVAR)
 {
     /* ES5 10.5 step 8 (with subsequent errata). */
     unsigned attrs = JSPROP_ENUMERATE;
     if (!regs.fp()->isEvalFrame())
         attrs |= JSPROP_PERMANENT;
-    if (op == JSOP_DEFCONST)
+    if (*regs.pc == JSOP_DEFCONST)
         attrs |= JSPROP_READONLY;
 
     /* Step 8b. */
     RootedObject &obj = rootObject0;
     obj = &regs.fp()->varObj();
 
     RootedPropertyName &name = rootName0;
     name = script->getName(regs.pc);
@@ -3099,20 +3145,20 @@ CASE(JSOP_SPREAD)
     regs.sp--;
 }
 END_CASE(JSOP_SPREAD)
 
 CASE(JSOP_GOSUB)
 {
     PUSH_BOOLEAN(false);
     int32_t i = (regs.pc - script->code) + JSOP_GOSUB_LENGTH;
-    len = GET_JUMP_OFFSET(regs.pc);
+    int32_t len = GET_JUMP_OFFSET(regs.pc);
     PUSH_INT32(i);
+    ADVANCE_AND_DISPATCH(len);
 }
-END_VARLEN_CASE
 
 CASE(JSOP_RETSUB)
 {
     /* Pop [exception or hole, retsub pc-index]. */
     Value rval, lval;
     POP_COPY_TO(rval);
     POP_COPY_TO(lval);
     JS_ASSERT(lval.isBoolean());
@@ -3124,19 +3170,19 @@ CASE(JSOP_RETSUB)
          * 350509, due to Igor Bukanov.
          */
         cx->setPendingException(rval);
         goto error;
     }
     JS_ASSERT(rval.isInt32());
 
     /* Increment the PC by this much. */
-    len = rval.toInt32() - int32_t(regs.pc - script->code);
+    int32_t len = rval.toInt32() - int32_t(regs.pc - script->code);
+    ADVANCE_AND_DISPATCH(len);
 }
-END_VARLEN_CASE
 
 CASE(JSOP_EXCEPTION)
 {
     PUSH_NULL();
     MutableHandleValue res = regs.stackHandleAt(-1);
     if (!GetAndClearException(cx, res))
         goto error;
 }
@@ -3210,17 +3256,17 @@ END_CASE(JSOP_DEBUGGER)
 
 CASE(JSOP_ENTERBLOCK)
 CASE(JSOP_ENTERLET0)
 CASE(JSOP_ENTERLET1)
 CASE(JSOP_ENTERLET2)
 {
     StaticBlockObject &blockObj = script->getObject(regs.pc)->as<StaticBlockObject>();
 
-    if (op == JSOP_ENTERBLOCK) {
+    if (*regs.pc == JSOP_ENTERBLOCK) {
         JS_ASSERT(regs.stackDepth() == blockObj.stackDepth());
         JS_ASSERT(regs.stackDepth() + blockObj.slotCount() <= script->nslots);
         Value *vp = regs.sp + blockObj.slotCount();
         SetValueRangeToUndefined(regs.sp, vp);
         regs.sp = vp;
     }
 
     /* Clone block iff there are any closed-over variables. */
@@ -3232,21 +3278,21 @@ END_CASE(JSOP_ENTERBLOCK)
 CASE(JSOP_LEAVEBLOCK)
 CASE(JSOP_LEAVEFORLETIN)
 CASE(JSOP_LEAVEBLOCKEXPR)
 {
     blockDepth = regs.fp()->blockChain().stackDepth();
 
     regs.fp()->popBlock(cx);
 
-    if (op == JSOP_LEAVEBLOCK) {
+    if (*regs.pc == JSOP_LEAVEBLOCK) {
         /* Pop the block's slots. */
         regs.sp -= GET_UINT16(regs.pc);
         JS_ASSERT(regs.stackDepth() == blockDepth);
-    } else if (op == JSOP_LEAVEBLOCKEXPR) {
+    } else if (*regs.pc == JSOP_LEAVEBLOCKEXPR) {
         /* Pop the block's slots maintaining the topmost expr. */
         Value *vp = &regs.sp[-1];
         regs.sp -= GET_UINT16(regs.pc);
         JS_ASSERT(regs.stackDepth() == blockDepth + 1);
         regs.sp[-1] = *vp;
     } else {
         /* Another op will pop; nothing to do here. */
         ADVANCE_AND_DISPATCH(JSOP_LEAVEFORLETIN_LENGTH);
@@ -3293,26 +3339,26 @@ CASE(JSOP_ARRAYPUSH)
     RootedObject &obj = rootObject0;
     obj = &regs.fp()->unaliasedLocal(slot).toObject();
     if (!js_NewbornArrayPush(cx, obj, regs.sp[-1]))
         goto error;
     regs.sp--;
 }
 END_CASE(JSOP_ARRAYPUSH)
 
-default:
+DEFAULT()
 {
     char numBuf[12];
-    JS_snprintf(numBuf, sizeof numBuf, "%d", op);
+    JS_snprintf(numBuf, sizeof numBuf, "%d", *regs.pc);
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                          JSMSG_BAD_BYTECODE, numBuf);
     goto error;
 }
 
-    } /* switch (op) */
+} /* interpreter loop */
 
     MOZ_ASSUME_UNREACHABLE("Interpreter loop exited via fallthrough");
 
   error:
     JS_ASSERT(uint32_t(regs.pc - script->code) < script->length);
 
     if (cx->isExceptionPending()) {
         /* Call debugger throw hooks. */
@@ -3355,28 +3401,36 @@ default:
                 /* 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.
                  */
-                ADVANCE_AND_DISPATCH(0);
+                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();
-                ADVANCE_AND_DISPATCH(0);
+
+                /*
+                 * 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;