Bug 1604467 - Replace SRC_TRY source note with a JSOP_TRY bytecode operand. r=tcampbell
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 02 Jan 2020 08:24:05 +0000
changeset 508589 a590ca97f13989c17e649988099e1d4b8aa6c397
parent 508588 7190108fee043a6caf3d6012740f32c550352cf7
child 508590 7060854d0302a4a2cbb386c755820d89cfbf4262
push id104096
push userjdemooij@mozilla.com
push dateThu, 02 Jan 2020 08:27:47 +0000
treeherderautoland@a590ca97f139 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1604467, 680228
milestone73.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 1604467 - Replace SRC_TRY source note with a JSOP_TRY bytecode operand. r=tcampbell JIT compilation and bytecode analysis now no longer depend on any source notes. The deleted code to print the offset-to-catch in Disassemble1 goes back to bug 680228, before the newer code coverage implementation. It's unlikely that anyone is still depending on this and it's also confusing to print an offset in the bytecode disassembly that's not actually an operand. Differential Revision: https://phabricator.services.mozilla.com/D58430
js/src/debugger/Script.cpp
js/src/frontend/SourceNotes.h
js/src/frontend/TryEmitter.cpp
js/src/frontend/TryEmitter.h
js/src/jit/BaselineCodeGen.cpp
js/src/jit/BytecodeAnalysis.cpp
js/src/jit/BytecodeAnalysis.h
js/src/jit/CompileInfo.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/shell/js.cpp
js/src/vm/BytecodeLocation.h
js/src/vm/BytecodeUtil.cpp
js/src/vm/BytecodeUtil.h
js/src/vm/Interpreter.cpp
js/src/vm/JSScript.cpp
js/src/vm/Opcodes.h
--- a/js/src/debugger/Script.cpp
+++ b/js/src/debugger/Script.cpp
@@ -1193,17 +1193,17 @@ class FlowGraphSummary {
         }
       } else if (op == JSOP_TRY) {
         // As there is no literal incoming edge into the catch block, we
         // make a fake one by copying the JSOP_TRY location, as-if this
         // was an incoming edge of the catch block. This is needed
         // because we only report offsets of entry points which have
         // valid incoming edges.
         for (const JSTryNote& tn : script->trynotes()) {
-          if (tn.start == r.frontOffset() + 1) {
+          if (tn.start == r.frontOffset() + JSOP_TRY_LENGTH) {
             uint32_t catchOffset = tn.start + tn.length;
             if (tn.kind == JSTRY_CATCH || tn.kind == JSTRY_FINALLY) {
               addEdge(lineno, column, catchOffset);
             }
           }
         }
       }
 
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -33,26 +33,16 @@ namespace js {
  * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
  *
  * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
  * enum, so its initializers need to match the order here.
  */
 
 class SrcNote {
  public:
-  // SRC_TRY: Source note for JSOP_TRY.
-  class Try {
-   public:
-    enum Fields {
-      // The offset of the JSOP_GOTO at the end of the try block from
-      // JSOP_TRY.
-      EndOfTryJumpOffset,
-      Count
-    };
-  };
   // SRC_COLSPAN: Source note for arbitrary ops.
   class ColSpan {
    public:
     enum Fields {
       // The column span (the diff between the column corresponds to the
       // current op and last known column).
       Span,
       Count
@@ -68,35 +58,35 @@ class SrcNote {
     };
   };
 };
 
 // clang-format off
 #define FOR_EACH_SRC_NOTE_TYPE(M)                                                                  \
     M(SRC_NULL,         "null",        0)  /* Terminates a note vector. */                         \
     M(SRC_ASSIGNOP,     "assignop",    0)  /* += or another assign-op follows. */                  \
-    M(SRC_TRY,          "try",         SrcNote::Try::Count) \
     /* All notes above here are "gettable".  See SN_IS_GETTABLE below. */                          \
     M(SRC_COLSPAN,      "colspan",     SrcNote::ColSpan::Count) \
     M(SRC_NEWLINE,      "newline",     0)  /* Bytecode follows a source newline. */                \
     M(SRC_SETLINE,      "setline",     SrcNote::SetLine::Count) \
     M(SRC_BREAKPOINT,   "breakpoint",  0)  /* Bytecode is a recommended breakpoint. */             \
     M(SRC_STEP_SEP,     "step-sep",    0)  /* Bytecode is the first in a new steppable area. */    \
+    M(SRC_UNUSED7,      "unused",      0) \
     M(SRC_XDELTA,       "xdelta",      0)  /* 8-15 (0b1xxx) are for extended delta notes. */
-    // Note: need to add a new source note? Consider bumping SRC_XDELTA to 12-15
-    // and change SN_XDELTA_BITS from 7 to 6.
+    // Note: need to add a new source note? If there's no SRC_UNUSED* note left,
+    // consider bumping SRC_XDELTA to 12-15 and change SN_XDELTA_BITS from 7 to 6.
 // clang-format on
 
 enum SrcNoteType {
 #define DEFINE_SRC_NOTE_TYPE(sym, name, arity) sym,
   FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_TYPE)
 #undef DEFINE_SRC_NOTE_TYPE
 
       SRC_LAST,
-  SRC_LAST_GETTABLE = SRC_TRY
+  SRC_LAST_GETTABLE = SRC_ASSIGNOP
 };
 
 static_assert(SRC_XDELTA == 8, "SRC_XDELTA should be 8");
 
 /* A source note array is terminated by an all-zero element. */
 inline void SN_MAKE_TERMINATOR(jssrcnote* sn) { *sn = SRC_NULL; }
 
 inline bool SN_IS_TERMINATOR(jssrcnote* sn) { return *sn == SRC_NULL; }
--- a/js/src/frontend/TryEmitter.cpp
+++ b/js/src/frontend/TryEmitter.cpp
@@ -19,18 +19,17 @@ using namespace js::frontend;
 
 using mozilla::Maybe;
 
 TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind)
     : bce_(bce),
       kind_(kind),
       controlKind_(controlKind),
       depth_(0),
-      noteIndex_(0),
-      tryStart_(0)
+      tryOpOffset_(0)
 #ifdef DEBUG
       ,
       state_(State::Start)
 #endif
 {
   if (controlKind_ == ControlKind::Syntactic) {
     controlInfo_.emplace(
         bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
@@ -44,24 +43,19 @@ bool TryEmitter::emitTry() {
   // we need to restore the stack and the scope chain before we transfer
   // the control to the exception handler.
   //
   // For that we store in a try note associated with the catch or
   // finally block the stack depth upon the try entry. The interpreter
   // uses this depth to properly unwind the stack and the scope chain.
   depth_ = bce_->bytecodeSection().stackDepth();
 
-  // Record the try location, then emit the try block.
-  if (!bce_->newSrcNote(SRC_TRY, &noteIndex_)) {
+  if (!bce_->emitN(JSOP_TRY, 4, &tryOpOffset_)) {
     return false;
   }
-  if (!bce_->emit1(JSOP_TRY)) {
-    return false;
-  }
-  tryStart_ = bce_->bytecodeSection().offset();
 
 #ifdef DEBUG
   state_ = State::Try;
 #endif
   return true;
 }
 
 bool TryEmitter::emitTryEnd() {
@@ -70,22 +64,21 @@ bool TryEmitter::emitTryEnd() {
 
   // GOSUB to finally, if present.
   if (hasFinally() && controlInfo_) {
     if (!bce_->emitGoSub(&controlInfo_->gosubs)) {
       return false;
     }
   }
 
-  // Source note points to the jump at the end of the try block.
-  if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::Try::EndOfTryJumpOffset,
-                              bce_->bytecodeSection().offset() - tryStart_ +
-                                  BytecodeOffsetDiff(JSOP_TRY_LENGTH))) {
-    return false;
-  }
+  // Patch the JSOP_TRY offset.
+  jsbytecode* trypc = bce_->bytecodeSection().code(tryOpOffset_);
+  BytecodeOffsetDiff offset = bce_->bytecodeSection().offset() - tryOpOffset_;
+  MOZ_ASSERT(*trypc == JSOP_TRY);
+  SET_CODE_OFFSET(trypc, offset.value());
 
   // Emit jump over catch and/or finally.
   if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_)) {
     return false;
   }
 
   if (!bce_->emitJumpTarget(&tryEnd_)) {
     return false;
@@ -271,26 +264,27 @@ bool TryEmitter::emitEnd() {
   // Fix up the end-of-try/catch jumps to come here.
   if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) {
     return false;
   }
 
   // Add the try note last, to let post-order give us the right ordering
   // (first to last for a given nesting level, inner to outer by level).
   if (hasCatch()) {
-    if (!bce_->addTryNote(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset)) {
+    if (!bce_->addTryNote(JSTRY_CATCH, depth_, offsetAfterTryOp(),
+                          tryEnd_.offset)) {
       return false;
     }
   }
 
   // If we've got a finally, mark try+catch region with additional
   // trynote to catch exceptions (re)thrown from a catch block or
   // for the try{}finally{} case.
   if (hasFinally()) {
-    if (!bce_->addTryNote(JSTRY_FINALLY, depth_, tryStart_,
+    if (!bce_->addTryNote(JSTRY_FINALLY, depth_, offsetAfterTryOp(),
                           finallyStart_.offset)) {
       return false;
     }
   }
 
 #ifdef DEBUG
   state_ = State::End;
 #endif
--- a/js/src/frontend/TryEmitter.h
+++ b/js/src/frontend/TryEmitter.h
@@ -66,22 +66,23 @@ class MOZ_STACK_CLASS TryEmitter {
   // See the comment for `controlInfo_`.
   //
   // The second one is whether the catch and finally blocks handle the frame's
   // return value.  For syntactic try-catch-finally, the bytecode marked with
   // "*" are emitted to clear return value with `undefined` before the catch
   // block and the finally block, and also to save/restore the return value
   // before/after the finally block.
   //
-  //     JSOP_TRY
+  //     JSOP_TRY offsetOf(jumpToEnd)
   //
   //     try_body...
   //
   //     JSOP_GOSUB finally
   //     JSOP_JUMPTARGET
+  //   jumpToEnd:
   //     JSOP_GOTO end:
   //
   //   catch:
   //     JSOP_JUMPTARGET
   //   * JSOP_UNDEFINED
   //   * JSOP_SETRVAL
   //
   //     catch_body...
@@ -130,21 +131,18 @@ class MOZ_STACK_CLASS TryEmitter {
   // Additionally, a finally block may be emitted for non-syntactic
   // try-catch-finally, even if the kind is TryCatch, because GOSUBs are not
   // emitted.
   mozilla::Maybe<TryFinallyControl> controlInfo_;
 
   // The stack depth before emitting JSOP_TRY.
   int depth_;
 
-  // The source note index for SRC_TRY.
-  unsigned noteIndex_;
-
-  // The offset after JSOP_TRY.
-  BytecodeOffset tryStart_;
+  // The offset of the JSOP_TRY op.
+  BytecodeOffset tryOpOffset_;
 
   // JSOP_JUMPTARGET after the entire try-catch-finally block.
   JumpList catchAndFinallyJump_;
 
   // The offset of JSOP_GOTO at the end of the try block.
   JumpTarget tryEnd_;
 
   // The offset of JSOP_JUMPTARGET at the beginning of the finally block.
@@ -183,16 +181,20 @@ class MOZ_STACK_CLASS TryEmitter {
 
   bool hasCatch() const {
     return kind_ == Kind::TryCatch || kind_ == Kind::TryCatchFinally;
   }
   bool hasFinally() const {
     return kind_ == Kind::TryCatchFinally || kind_ == Kind::TryFinally;
   }
 
+  BytecodeOffset offsetAfterTryOp() const {
+    return tryOpOffset_ + BytecodeOffsetDiff(JSOP_TRY_LENGTH);
+  }
+
  public:
   TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind);
 
   MOZ_MUST_USE bool emitTry();
   MOZ_MUST_USE bool emitCatch();
 
   // If `finallyPos` is specified, it's an offset of the finally block's
   // "{" character in the source code text, to improve line:column number in
--- a/js/src/jit/BaselineCodeGen.cpp
+++ b/js/src/jit/BaselineCodeGen.cpp
@@ -90,17 +90,17 @@ BaselineCompiler::BaselineCompiler(JSCon
   MOZ_CRASH();
 #endif
 }
 
 BaselineInterpreterGenerator::BaselineInterpreterGenerator(JSContext* cx)
     : BaselineCodeGen(cx /* no handlerArgs */) {}
 
 bool BaselineCompilerHandler::init(JSContext* cx) {
-  if (!analysis_.init(alloc_, cx->caches().gsnCache)) {
+  if (!analysis_.init(alloc_)) {
     return false;
   }
 
   uint32_t len = script_->length();
 
   if (!labels_.init(alloc_, len)) {
     return false;
   }
--- a/js/src/jit/BytecodeAnalysis.cpp
+++ b/js/src/jit/BytecodeAnalysis.cpp
@@ -31,17 +31,17 @@ struct CatchFinallyRange {
     MOZ_ASSERT(end > start);
   }
 
   bool contains(uint32_t offset) const {
     return start <= offset && offset < end;
   }
 };
 
-bool BytecodeAnalysis::init(TempAllocator& alloc, GSNCache& gsn) {
+bool BytecodeAnalysis::init(TempAllocator& alloc) {
   if (!infos_.growByUninitialized(script_->length())) {
     return false;
   }
 
   // Clear all BytecodeInfo.
   mozilla::PodZero(infos_.begin(), infos_.length());
   infos_[0].init(/*stackDepth=*/0);
 
@@ -96,33 +96,28 @@ bool BytecodeAnalysis::init(TempAllocato
             infos_[targetOffset].jumpTarget = true;
           }
         }
         break;
       }
 
       case JSOP_TRY: {
         for (const JSTryNote& tn : script_->trynotes()) {
-          if (tn.start == offset + 1 &&
+          if (tn.start == offset + JSOP_TRY_LENGTH &&
               (tn.kind == JSTRY_CATCH || tn.kind == JSTRY_FINALLY)) {
             uint32_t catchOrFinallyOffset = tn.start + tn.length;
             infos_[catchOrFinallyOffset].init(stackDepth);
             infos_[catchOrFinallyOffset].jumpTarget = true;
           }
         }
 
         // Get the pc of the last instruction in the try block. It's a JSOP_GOTO
         // to jump over the catch/finally blocks.
-        jssrcnote* sn = GetSrcNote(gsn, script_, it.toRawBytecode());
-        MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY);
-
-        BytecodeLocation endOfTryLoc(
-            script_,
-            it.toRawBytecode() +
-                GetSrcNoteOffset(sn, SrcNote::Try::EndOfTryJumpOffset));
+        BytecodeLocation endOfTryLoc(script_,
+                                     it.toRawBytecode() + it.codeOffset());
         MOZ_ASSERT(endOfTryLoc.is(JSOP_GOTO));
 
         BytecodeLocation afterTryLoc(
             script_, endOfTryLoc.toRawBytecode() + endOfTryLoc.jumpOffset());
         MOZ_ASSERT(afterTryLoc > endOfTryLoc);
 
         // Ensure the code following the try-block is always marked as
         // reachable, to simplify Ion's ControlFlowGenerator.
--- a/js/src/jit/BytecodeAnalysis.h
+++ b/js/src/jit/BytecodeAnalysis.h
@@ -40,17 +40,17 @@ class BytecodeAnalysis {
   JSScript* script_;
   Vector<BytecodeInfo, 0, JitAllocPolicy> infos_;
 
   bool hasTryFinally_;
 
  public:
   explicit BytecodeAnalysis(TempAllocator& alloc, JSScript* script);
 
-  MOZ_MUST_USE bool init(TempAllocator& alloc, GSNCache& gsn);
+  MOZ_MUST_USE bool init(TempAllocator& alloc);
 
   BytecodeInfo& info(jsbytecode* pc) {
     uint32_t pcOffset = script_->pcToOffset(pc);
     MOZ_ASSERT(infos_[pcOffset].initialized);
     return infos_[pcOffset];
   }
 
   BytecodeInfo* maybeInfo(jsbytecode* pc) {
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -292,20 +292,16 @@ class CompileInfo {
   JSObject* getObject(jsbytecode* pc) const {
     return script_->getObject(GET_UINT32_INDEX(pc));
   }
 
   inline JSFunction* getFunction(jsbytecode* pc) const;
 
   BigInt* getBigInt(jsbytecode* pc) const { return script_->getBigInt(pc); }
 
-  jssrcnote* getNote(GSNCache& gsn, jsbytecode* pc) const {
-    return GetSrcNote(gsn, script(), pc);
-  }
-
   // Total number of slots: args, locals, and stack.
   unsigned nslots() const { return nslots_; }
 
   // Number of slots needed for env chain, return value,
   // maybe argumentsobject and this value.
   unsigned nimplicit() const { return nimplicit_; }
   // Number of arguments (without counting this value).
   unsigned nargs() const { return nargs_; }
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1646,23 +1646,20 @@ class MOZ_RAII PoppedValueUseChecker {
   }
 };
 #endif
 
 AbortReasonOr<Ok> IonBuilder::traverseBytecode() {
   // See the "Control Flow handling in IonBuilder" comment in IonBuilder.h for
   // more information.
 
-  // IonBuilder's destructor is not called, so make sure pendingEdges_ and
-  // GSNCache are not holding onto malloc memory when we return.
+  // IonBuilder's destructor is not called, so make sure pendingEdges_ is not
+  // holding onto malloc memory when we return.
   pendingEdges_.emplace();
-  auto freeMemory = mozilla::MakeScopeExit([&] {
-    pendingEdges_.reset();
-    gsn.purge();
-  });
+  auto freeMemory = mozilla::MakeScopeExit([&] { pendingEdges_.reset(); });
 
   MOZ_TRY(startTraversingBlock(current));
 
   const jsbytecode* const codeEnd = script()->codeEnd();
 
   while (true) {
     if (!alloc().ensureBallast()) {
       return abort(AbortReason::Alloc);
@@ -3215,23 +3212,19 @@ AbortReasonOr<Ok> IonBuilder::visitTry()
   }
 
   // Try-catch during analyses is not yet supported. Code within the 'catch'
   // block is not accounted for.
   if (info().isAnalysis()) {
     return abort(AbortReason::Disable, "Try-catch during analysis");
   }
 
-  jssrcnote* sn = GetSrcNote(gsn, script(), pc);
-  MOZ_ASSERT(SN_TYPE(sn) == SRC_TRY);
-
   // Get the pc of the last instruction in the try block. It's a JSOP_GOTO to
   // jump over the catch block.
-  jsbytecode* endpc =
-      pc + GetSrcNoteOffset(sn, SrcNote::Try::EndOfTryJumpOffset);
+  jsbytecode* endpc = pc + GET_CODE_OFFSET(pc);
   MOZ_ASSERT(JSOp(*endpc) == JSOP_GOTO);
   MOZ_ASSERT(GET_JUMP_OFFSET(endpc) > 0);
 
   jsbytecode* afterTry = endpc + GET_JUMP_OFFSET(endpc);
 
   // The baseline compiler should not attempt to enter the catch block
   // via OSR.
   MOZ_ASSERT(info().osrPc() < endpc || info().osrPc() >= afterTry);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -1196,17 +1196,16 @@ class IonBuilder : public MIRGenerator,
   TIOracle tiOracle_;
 
   TemporaryTypeSet* thisTypes;
   TemporaryTypeSet* argTypes;
   TemporaryTypeSet* typeArray;
   uint32_t typeArrayHint;
   uint32_t* bytecodeTypeMap;
 
-  GSNCache gsn;
   jsbytecode* pc;
   jsbytecode* nextpc = nullptr;
 
   // The current MIR block. This can be nullptr after a block has been
   // terminated, for example right after a 'return' or 'break' statement.
   MBasicBlock* current = nullptr;
 
   uint32_t loopDepth_;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3068,25 +3068,16 @@ static MOZ_MUST_USE bool SrcNotes(JSCont
           return false;
         }
         break;
 
       case SRC_NEWLINE:
         ++lineno;
         break;
 
-      case SRC_TRY:
-        MOZ_ASSERT(JSOp(script->code()[offset]) == JSOP_TRY);
-        if (!sp->jsprintf(" offset to jump %u",
-                          unsigned(GetSrcNoteOffset(
-                              sn, SrcNote::Try::EndOfTryJumpOffset)))) {
-          return false;
-        }
-        break;
-
       default:
         MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
     }
     if (!sp->put("\n")) {
       return false;
     }
   }
 
--- a/js/src/vm/BytecodeLocation.h
+++ b/js/src/vm/BytecodeLocation.h
@@ -82,16 +82,17 @@ class BytecodeLocation {
 
   uint32_t getTableSwitchDefaultOffset(const JSScript* script) const;
 
   uint32_t useCount() const;
 
   uint32_t defCount() const;
 
   int32_t jumpOffset() const { return GET_JUMP_OFFSET(rawBytecode_); }
+  int32_t codeOffset() const { return GET_CODE_OFFSET(rawBytecode_); }
 
   PropertyName* getPropertyName(const JSScript* script) const;
 
 #ifdef DEBUG
   bool hasSameScript(const BytecodeLocation& other) const {
     return debugOnlyScript_ == other.debugOnlyScript_;
   }
 #endif
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -902,17 +902,17 @@ bool BytecodeParser::parse() {
 
       case JSOP_TRY: {
         // Everything between a try and corresponding catch or finally is
         // conditional. Note that there is no problem with code which is skipped
         // by a thrown exception but is not caught by a later handler in the
         // same function: no more code will execute, and it does not matter what
         // is defined.
         for (const JSTryNote& tn : script_->trynotes()) {
-          if (tn.start == offset + 1) {
+          if (tn.start == offset + JSOP_TRY_LENGTH) {
             uint32_t catchOffset = tn.start + tn.length;
             if (tn.kind == JSTRY_CATCH) {
               if (!addJump(catchOffset, stackDepth, offsetStack, pc,
                            JumpKind::TryCatch)) {
                 return false;
               }
             } else if (tn.kind == JSTRY_FINALLY) {
               if (!addJump(catchOffset, stackDepth, offsetStack, pc,
@@ -1397,41 +1397,34 @@ static unsigned Disassemble1(JSContext* 
   }
   if (!sp->jsprintf("  %s", CodeName[op])) {
     return 0;
   }
 
   int i;
   switch (JOF_TYPE(cs->format)) {
     case JOF_BYTE:
-      // Scan the trynotes to find the associated catch block
-      // and make the try opcode look like a jump instruction
-      // with an offset. This simplifies code coverage analysis
-      // based on this disassembled output.
-      if (op == JSOP_TRY) {
-        for (const JSTryNote& tn : script->trynotes()) {
-          if (tn.kind == JSTRY_CATCH && tn.start == loc + 1) {
-            if (!sp->jsprintf(" %u (%+d)", unsigned(loc + tn.length + 1),
-                              int(tn.length + 1))) {
-              return 0;
-            }
-            break;
-          }
-        }
-      }
       break;
 
     case JOF_JUMP: {
       ptrdiff_t off = GET_JUMP_OFFSET(pc);
       if (!sp->jsprintf(" %u (%+d)", unsigned(loc + int(off)), int(off))) {
         return 0;
       }
       break;
     }
 
+    case JOF_CODE_OFFSET: {
+      ptrdiff_t off = GET_CODE_OFFSET(pc);
+      if (!sp->jsprintf(" %u (%+d)", unsigned(loc + int(off)), int(off))) {
+        return 0;
+      }
+      break;
+    }
+
     case JOF_SCOPE: {
       RootedScope scope(cx, script->getScope(GET_UINT32_INDEX(pc)));
       UniqueChars bytes;
       if (!ToDisassemblySource(cx, scope, &bytes)) {
         return 0;
       }
       if (!sp->jsprintf(" %s", bytes.get())) {
         return 0;
--- a/js/src/vm/BytecodeUtil.h
+++ b/js/src/vm/BytecodeUtil.h
@@ -58,16 +58,17 @@ enum {
   JOF_OBJECT = 15,       /* uint32_t object index */
   JOF_REGEXP = 16,       /* uint32_t regexp index */
   JOF_DOUBLE = 17,       /* inline DoubleValue */
   JOF_SCOPE = 18,        /* uint32_t scope index */
   JOF_ICINDEX = 19,      /* uint32_t IC index */
   JOF_LOOPHEAD = 20,     /* JSOP_LOOPHEAD, combines JOF_ICINDEX and JOF_UINT8 */
   JOF_BIGINT = 21,       /* uint32_t index for BigInt value */
   JOF_CLASS_CTOR = 22,   /* uint32_t atom index, sourceStart, sourceEnd */
+  JOF_CODE_OFFSET = 23,  /* int32_t bytecode offset */
   JOF_TYPEMASK = 0x001f, /* mask for above immediate types */
 
   JOF_NAME = 1 << 5,     /* name operation */
   JOF_PROP = 2 << 5,     /* obj.prop operation */
   JOF_ELEM = 3 << 5,     /* obj[index] operation */
   JOF_MODEMASK = 3 << 5, /* mask for above addressing modes */
 
   JOF_PROPSET = 1 << 7,      /* property/element/name set operation */
@@ -185,16 +186,24 @@ static MOZ_ALWAYS_INLINE void SET_INT32(
 static MOZ_ALWAYS_INLINE int32_t GET_JUMP_OFFSET(jsbytecode* pc) {
   return GET_INT32(pc);
 }
 
 static MOZ_ALWAYS_INLINE void SET_JUMP_OFFSET(jsbytecode* pc, int32_t off) {
   SET_INT32(pc, off);
 }
 
+static MOZ_ALWAYS_INLINE int32_t GET_CODE_OFFSET(jsbytecode* pc) {
+  return GET_INT32(pc);
+}
+
+static MOZ_ALWAYS_INLINE void SET_CODE_OFFSET(jsbytecode* pc, int32_t off) {
+  SET_INT32(pc, off);
+}
+
 static const unsigned UINT32_INDEX_LEN = 4;
 
 static MOZ_ALWAYS_INLINE uint32_t GET_UINT32_INDEX(const jsbytecode* pc) {
   return GET_UINT32(pc);
 }
 
 static MOZ_ALWAYS_INLINE void SET_UINT32_INDEX(jsbytecode* pc, uint32_t index) {
   SET_UINT32(pc, index);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1895,26 +1895,27 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
       /* Commence executing the actual opcode. */
       SANITY_CHECKS();
       DISPATCH_TO(op);
     }
 
     /* Various 1-byte no-ops. */
     CASE(JSOP_NOP)
     CASE(JSOP_NOP_DESTRUCTURING)
-    CASE(JSOP_TRY_DESTRUCTURING)
-    CASE(JSOP_TRY) {
+    CASE(JSOP_TRY_DESTRUCTURING) {
       MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
       ADVANCE_AND_DISPATCH(1);
     }
 
-    CASE(JSOP_JUMPTARGET) {
+    CASE(JSOP_TRY)
+    END_CASE(JSOP_TRY)
+
+    CASE(JSOP_JUMPTARGET)
       COUNT_COVERAGE();
-      ADVANCE_AND_DISPATCH(JSOP_JUMPTARGET_LENGTH);
-    }
+    END_CASE(JSOP_JUMPTARGET)
 
     CASE(JSOP_LOOPHEAD) {
       COUNT_COVERAGE();
 
       // Attempt on-stack replacement into the Baseline Interpreter.
       if (jit::IsBaselineInterpreterEnabled()) {
         script->incWarmUpCounter();
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4618,28 +4618,26 @@ void JSScript::assertValidJumpTargets() 
         MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc);
         MOZ_ASSERT(switchCase.isJumpTarget());
       }
     }
   }
 
   // Check catch/finally blocks as jump targets.
   for (const JSTryNote& tn : trynotes()) {
-    jsbytecode* end = codeEnd();
-    jsbytecode* mainEntry = main();
-
-    jsbytecode* tryStart = offsetToPC(tn.start);
-    jsbytecode* tryPc = tryStart - 1;
     if (tn.kind != JSTRY_CATCH && tn.kind != JSTRY_FINALLY) {
       continue;
     }
 
+    jsbytecode* tryStart = offsetToPC(tn.start);
+    jsbytecode* tryPc = tryStart - JSOP_TRY_LENGTH;
     MOZ_ASSERT(JSOp(*tryPc) == JSOP_TRY);
+
     jsbytecode* tryTarget = tryStart + tn.length;
-    MOZ_ASSERT(mainEntry <= tryTarget && tryTarget < end);
+    MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd());
     MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget)));
   }
 }
 #endif
 
 size_t JSScript::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const {
   return mallocSizeOf(data_);
 }
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2265,25 +2265,29 @@
      *   Type: Local Variables
      *   Operands: uint24_t localno
      *   Stack: v => v
      */ \
     MACRO(JSOP_THROWSETCONST, "throwsetconst", NULL, 4, 1, 1, JOF_LOCAL|JOF_NAME|JOF_DETECTING) \
     /*
      * This no-op appears at the top of the bytecode for a 'TryStatement'.
      *
+     * The jumpAtEndOffset operand is the offset (relative to the current op) of
+     * the JSOP_GOTO at the end of the try-block body. This is used by bytecode
+     * analysis and JIT compilation.
+     *
      * Location information for catch/finally blocks is stored in a side table,
      * 'script->trynotes()'.
      *
      *   Category: Statements
      *   Type: Exception Handling
-     *   Operands:
+     *   Operands: int32_t jumpAtEndOffset
      *   Stack: =>
      */ \
-    MACRO(JSOP_TRY, "try", NULL, 1, 0, 0, JOF_BYTE) \
+    MACRO(JSOP_TRY, "try", NULL, 5, 0, 0, JOF_CODE_OFFSET) \
     /*
      * No-op used by the exception unwinder to determine the correct
      * environment to unwind to when performing IteratorClose due to
      * destructuring.
      *
      *   Category: Other
      *   Operands:
      *   Stack: =>