Bug 1322019 - Part 5: Print stack transition in dis() function output. r=nbp
authorTooru Fujisawa <arai_a@mac.com>
Mon, 27 Feb 2017 20:02:56 +0900
changeset 374083 2c034824894414f8c69f124588b6d9e4b655c291
parent 374082 445ad080f55cd1f6a3dd99a6656645e97f45b748
child 374084 5e464ee0fb565d7aa21536fac6a880a8c3264db0
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1322019
milestone54.0a1
Bug 1322019 - Part 5: Print stack transition in dis() function output. r=nbp
js/src/jsopcode.cpp
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -10,16 +10,17 @@
 
 #include "jsopcodeinlines.h"
 
 #define __STDC_FORMAT_MACROS
 
 #include "mozilla/Attributes.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/Sprintf.h"
+#include "mozilla/Vector.h"
 
 #include <algorithm>
 #include <ctype.h>
 #include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
 
 #include "jsapi.h"
@@ -249,119 +250,317 @@ js::DumpCompartmentPCCounts(JSContext* c
 
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////
 // Bytecode Parser
 /////////////////////////////////////////////////////////////////////
 
+// Stores the information about the stack slot, where the value comes from.
+// Elements of BytecodeParser::Bytecode.{offsetStack,offsetStackAfter} arrays.
+struct OffsetAndDefIndex {
+    // To make this struct a POD type, keep these properties public.
+    // Use accessors instead of directly accessing them.
+
+    // The offset of the PC that pushed the value for this slot.
+    uint32_t offset_;
+
+    // The index in `ndefs` for the PC (0-origin)
+    uint8_t defIndex_;
+
+    enum : uint8_t {
+        Normal = 0,
+
+        // Ignored this value in the expression decompilation.
+        // Used by JSOP_NOP_DESTRUCTURING.  See BytecodeParser::simulateOp.
+        Ignored,
+
+        // The value in this slot comes from 2 or more paths.
+        // offset_ and defIndex_ holds the information for the path that
+        // reaches here first.
+        Merged,
+    } type_;
+
+    uint32_t offset() const {
+        MOZ_ASSERT(!isSpecial());
+        return offset_;
+    };
+    uint32_t specialOffset() const {
+        MOZ_ASSERT(isSpecial());
+        return offset_;
+    };
+
+    uint8_t defIndex() const {
+        MOZ_ASSERT(!isSpecial());
+        return defIndex_;
+    }
+    uint8_t specialDefIndex() const {
+        MOZ_ASSERT(isSpecial());
+        return defIndex_;
+    }
+
+    bool isSpecial() const {
+        return type_ != Normal;
+    }
+    bool isMerged() const {
+        return type_ == Merged;
+    }
+    bool isIgnored() const {
+        return type_ == Ignored;
+    }
+
+    void set(uint32_t aOffset, uint8_t aDefIndex) {
+        offset_ = aOffset;
+        defIndex_ = aDefIndex;
+        type_ = Normal;
+    }
+
+    // Keep offset_ and defIndex_ values for stack dump.
+    void setMerged() {
+        type_ = Merged;
+    }
+    void setIgnored() {
+        type_ = Ignored;
+    }
+
+    bool operator==(const OffsetAndDefIndex& rhs) const {
+        return offset_ == rhs.offset_ &&
+               defIndex_ == rhs.defIndex_;
+    }
+
+    bool operator!=(const OffsetAndDefIndex& rhs) const {
+        return !(*this == rhs);
+    }
+};
+
+namespace mozilla {
+
+template <>
+struct IsPod<OffsetAndDefIndex> : TrueType {};
+
+} // namespace mozilla
+
 namespace {
 
 class BytecodeParser
 {
+  public:
+    enum class JumpKind {
+        Simple,
+        SwitchCase,
+        SwitchDefault,
+        TryCatch,
+        TryFinally
+    };
+
+  private:
     class Bytecode
     {
       public:
-        Bytecode() { mozilla::PodZero(this); }
+        explicit Bytecode(const LifoAllocPolicy<Fallible>& alloc)
+          : parsed(false),
+            stackDepth(0),
+            offsetStack(nullptr)
+#ifdef DEBUG
+            ,
+            stackDepthAfter(0),
+            offsetStackAfter(nullptr),
+            jumpOrigins(alloc)
+#endif /* DEBUG */
+        {}
 
         // Whether this instruction has been analyzed to get its output defines
         // and stack.
         bool parsed : 1;
 
         // Stack depth before this opcode.
         uint32_t stackDepth;
 
         // Pointer to array of |stackDepth| offsets.  An element at position N
         // in the array is the offset of the opcode that defined the
         // corresponding stack slot.  The top of the stack is at position
         // |stackDepth - 1|.
-        uint32_t* offsetStack;
-
-        bool captureOffsetStack(LifoAlloc& alloc, const uint32_t* stack, uint32_t depth) {
+        OffsetAndDefIndex* offsetStack;
+
+#ifdef DEBUG
+        // stack depth after this opcode.
+        uint32_t stackDepthAfter;
+
+        // Pointer to array of |stackDepthAfter| offsets.
+        OffsetAndDefIndex* offsetStackAfter;
+
+        struct JumpInfo {
+            uint32_t from;
+            JumpKind kind;
+
+            JumpInfo(uint32_t from_, JumpKind kind_)
+              : from(from_),
+                kind(kind_)
+            {}
+        };
+
+        // A list of offsets of the bytecode that jumps to this bytecode,
+        // exclusing previous bytecode.
+        Vector<JumpInfo, 0, LifoAllocPolicy<Fallible>> jumpOrigins;
+#endif /* DEBUG */
+
+        bool captureOffsetStack(LifoAlloc& alloc, const OffsetAndDefIndex* stack, uint32_t depth) {
             stackDepth = depth;
-            offsetStack = alloc.newArray<uint32_t>(stackDepth);
+            offsetStack = alloc.newArray<OffsetAndDefIndex>(stackDepth);
             if (!offsetStack)
                 return false;
             if (stackDepth) {
                 for (uint32_t n = 0; n < stackDepth; n++)
                     offsetStack[n] = stack[n];
             }
             return true;
         }
 
+#ifdef DEBUG
+        bool captureOffsetStackAfter(LifoAlloc& alloc, const OffsetAndDefIndex* stack,
+                                     uint32_t depth) {
+            stackDepthAfter = depth;
+            offsetStackAfter = alloc.newArray<OffsetAndDefIndex>(stackDepthAfter);
+            if (!offsetStackAfter)
+                return false;
+            if (stackDepthAfter) {
+                for (uint32_t n = 0; n < stackDepthAfter; n++)
+                    offsetStackAfter[n] = stack[n];
+            }
+            return true;
+        }
+
+        bool addJump(uint32_t from, JumpKind kind) {
+            return jumpOrigins.append(JumpInfo(from, kind));
+        }
+#endif /* DEBUG */
+
         // When control-flow merges, intersect the stacks, marking slots that
-        // are defined by different offsets with the UnknownOffset sentinel.
+        // are defined by different offsets and/or defIndices merged.
         // This is sufficient for forward control-flow.  It doesn't grok loops
         // -- for that you would have to iterate to a fixed point -- but there
         // shouldn't be operands on the stack at a loop back-edge anyway.
-        void mergeOffsetStack(const uint32_t* stack, uint32_t depth) {
+        void mergeOffsetStack(const OffsetAndDefIndex* stack, uint32_t depth) {
             MOZ_ASSERT(depth == stackDepth);
             for (uint32_t n = 0; n < stackDepth; n++) {
-                if (stack[n] == SpecialOffsets::IgnoreOffset)
+                if (stack[n].isIgnored())
                     continue;
-                if (offsetStack[n] == SpecialOffsets::IgnoreOffset)
+                if (offsetStack[n].isIgnored())
                     offsetStack[n] = stack[n];
                 if (offsetStack[n] != stack[n])
-                    offsetStack[n] = SpecialOffsets::UnknownOffset;
+                    offsetStack[n].setMerged();
             }
         }
     };
 
     JSContext* cx_;
     LifoAllocScope allocScope_;
     RootedScript script_;
 
     Bytecode** codeArray_;
 
-    // Use a struct instead of an enum class to avoid casting the enumerated
-    // value.
-    struct SpecialOffsets {
-        static const uint32_t UnknownOffset = UINT32_MAX;
-        static const uint32_t IgnoreOffset = UINT32_MAX - 1;
-        static const uint32_t FirstSpecialOffset = IgnoreOffset;
-    };
+#ifdef DEBUG
+    // Dedicated mode for stack dump.
+    // Capture stack after each opcode, and also enable special handling for
+    // some opcodes to make stack transition clearer.
+    bool isStackDump;
+#endif /* DEBUG */
 
   public:
     BytecodeParser(JSContext* cx, JSScript* script)
       : cx_(cx),
         allocScope_(&cx->tempLifoAlloc()),
         script_(cx, script),
-        codeArray_(nullptr) { }
+        codeArray_(nullptr)
+#ifdef DEBUG
+        ,
+        isStackDump(false)
+#endif /* DEBUG */
+    {}
 
     bool parse();
 
 #ifdef DEBUG
     bool isReachable(uint32_t offset) { return maybeCode(offset); }
     bool isReachable(const jsbytecode* pc) { return maybeCode(pc); }
-#endif
+#endif /* DEBUG */
 
     uint32_t stackDepthAtPC(uint32_t offset) {
         // Sometimes the code generator in debug mode asks about the stack depth
         // of unreachable code (bug 932180 comment 22).  Assume that unreachable
         // code has no operands on the stack.
         return getCode(offset).stackDepth;
     }
-    uint32_t stackDepthAtPC(const jsbytecode* pc) { return stackDepthAtPC(script_->pcToOffset(pc)); }
-
-    uint32_t offsetForStackOperand(uint32_t offset, int operand) {
+    uint32_t stackDepthAtPC(const jsbytecode* pc) {
+        return stackDepthAtPC(script_->pcToOffset(pc));
+    }
+
+#ifdef DEBUG
+    uint32_t stackDepthAfterPC(uint32_t offset) {
+        return getCode(offset).stackDepthAfter;
+    }
+    uint32_t stackDepthAfterPC(const jsbytecode* pc) {
+        return stackDepthAfterPC(script_->pcToOffset(pc));
+    }
+#endif
+
+    const OffsetAndDefIndex& offsetForStackOperand(uint32_t offset, int operand) {
         Bytecode& code = getCode(offset);
         if (operand < 0) {
             operand += code.stackDepth;
             MOZ_ASSERT(operand >= 0);
         }
         MOZ_ASSERT(uint32_t(operand) < code.stackDepth);
         return code.offsetStack[operand];
     }
-    jsbytecode* pcForStackOperand(jsbytecode* pc, int operand) {
-        uint32_t offset = offsetForStackOperand(script_->pcToOffset(pc), operand);
-        if (offset >= SpecialOffsets::FirstSpecialOffset)
+    jsbytecode* pcForStackOperand(jsbytecode* pc, int operand, uint8_t* defIndex) {
+        size_t offset = script_->pcToOffset(pc);
+        const OffsetAndDefIndex& offsetAndDefIndex = offsetForStackOperand(offset, operand);
+        if (offsetAndDefIndex.isSpecial())
             return nullptr;
-        return script_->offsetToPC(offset);
+        *defIndex = offsetAndDefIndex.defIndex();
+        return script_->offsetToPC(offsetAndDefIndex.offset());
+    }
+
+#ifdef DEBUG
+    const OffsetAndDefIndex& offsetForStackOperandAfterPC(uint32_t offset, int operand) {
+        Bytecode& code = getCode(offset);
+        if (operand < 0) {
+            operand += code.stackDepthAfter;
+            MOZ_ASSERT(operand >= 0);
+        }
+        MOZ_ASSERT(uint32_t(operand) < code.stackDepthAfter);
+        return code.offsetStackAfter[operand];
     }
+    jsbytecode* pcForStackOperandAfterPC(jsbytecode* pc, int operand, uint8_t* defIndex) {
+        size_t offset = script_->pcToOffset(pc);
+        const OffsetAndDefIndex& offsetAndDefIndex = offsetForStackOperandAfterPC(offset, operand);
+        if (offsetAndDefIndex.isSpecial())
+            return nullptr;
+        *defIndex = offsetAndDefIndex.defIndex();
+        return script_->offsetToPC(offsetAndDefIndex.offset());
+    }
+
+    template <typename Callback>
+    bool forEachJumpOrigins(jsbytecode* pc, Callback callback) {
+        Bytecode& code = getCode(script_->pcToOffset(pc));
+
+        for (Bytecode::JumpInfo& info : code.jumpOrigins) {
+            if (!callback(script_->offsetToPC(info.from), info.kind))
+                return false;
+        }
+
+        return true;
+    }
+
+    void setStackDump() {
+        isStackDump = true;
+    }
+#endif /* DEBUG */
 
   private:
     LifoAlloc& alloc() {
         return allocScope_.alloc();
     }
 
     void reportOOM() {
         allocScope_.releaseEarly();
@@ -385,48 +584,98 @@ class BytecodeParser
     Bytecode& getCode(const jsbytecode* pc) { return getCode(script_->pcToOffset(pc)); }
 
     Bytecode* maybeCode(uint32_t offset) {
         MOZ_ASSERT(offset < script_->length());
         return codeArray_[offset];
     }
     Bytecode* maybeCode(const jsbytecode* pc) { return maybeCode(script_->pcToOffset(pc)); }
 
-    uint32_t simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint32_t stackDepth);
-
-    inline bool recordBytecode(uint32_t offset, const uint32_t* offsetStack, uint32_t stackDepth);
+    uint32_t simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
+                        uint32_t stackDepth);
+
+    inline bool recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
+                               uint32_t stackDepth);
 
     inline bool addJump(uint32_t offset, uint32_t* currentOffset,
-                        uint32_t stackDepth, const uint32_t* offsetStack);
+                        uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
+                        jsbytecode* pc, JumpKind kind);
 };
 
 }  // anonymous namespace
 
 uint32_t
-BytecodeParser::simulateOp(JSOp op, uint32_t offset, uint32_t* offsetStack, uint32_t stackDepth)
+BytecodeParser::simulateOp(JSOp op, uint32_t offset, OffsetAndDefIndex* offsetStack,
+                           uint32_t stackDepth)
 {
     uint32_t nuses = GetUseCount(script_, offset);
     uint32_t ndefs = GetDefCount(script_, offset);
 
     MOZ_ASSERT(stackDepth >= nuses);
     stackDepth -= nuses;
     MOZ_ASSERT(stackDepth + ndefs <= maximumStackDepth());
 
+#ifdef DEBUG
+    if (isStackDump) {
+        // Opcodes that modifies the object but keeps it on the stack while
+        // initialization should be listed here instead of switch below.
+        // For error message, they shouldn't be shown as the original object
+        // like "[]" after adding elements.
+        // For stack dump, keeping the input is better.
+        switch (op) {
+          case JSOP_INITELEM_ARRAY:
+          case JSOP_INITHIDDENPROP:
+          case JSOP_INITHIDDENPROP_GETTER:
+          case JSOP_INITHIDDENPROP_SETTER:
+          case JSOP_INITLOCKEDPROP:
+          case JSOP_INITPROP:
+          case JSOP_INITPROP_GETTER:
+          case JSOP_INITPROP_SETTER:
+          case JSOP_SETFUNNAME:
+            // Keep the second value.
+            MOZ_ASSERT(nuses == 2);
+            MOZ_ASSERT(ndefs == 1);
+            goto end;
+
+          case JSOP_INITELEM:
+          case JSOP_INITELEM_GETTER:
+          case JSOP_INITELEM_SETTER:
+          case JSOP_INITHIDDENELEM:
+          case JSOP_INITHIDDENELEM_GETTER:
+          case JSOP_INITHIDDENELEM_SETTER:
+            // Keep the third value.
+            MOZ_ASSERT(nuses == 3);
+            MOZ_ASSERT(ndefs == 1);
+            goto end;
+
+          case JSOP_INITELEM_INC:
+            // Keep the third value and push one more value.
+            MOZ_ASSERT(nuses == 3);
+            MOZ_ASSERT(ndefs == 2);
+            offsetStack[stackDepth + 1].set(offset, 1);
+            goto end;
+
+          default:
+            break;
+        }
+    }
+#endif /* DEBUG */
+
     // Mark the current offset as defining its values on the offset stack,
     // unless it just reshuffles the stack.  In that case we want to preserve
     // the opcode that generated the original value.
     switch (op) {
       default:
         for (uint32_t n = 0; n != ndefs; ++n)
-            offsetStack[stackDepth + n] = offset;
+            offsetStack[stackDepth + n].set(offset, n);
         break;
 
       case JSOP_NOP_DESTRUCTURING:
         // Poison the last offset to not obfuscate the error message.
-        offsetStack[stackDepth - 1] = SpecialOffsets::IgnoreOffset;
+        offsetStack[stackDepth - 1].setIgnored();
         break;
 
       case JSOP_CASE:
         // Keep the switch value.
         MOZ_ASSERT(ndefs == 1);
         break;
 
       case JSOP_DUP:
@@ -446,40 +695,40 @@ BytecodeParser::simulateOp(JSOp op, uint
         unsigned n = GET_UINT24(pc);
         MOZ_ASSERT(n < stackDepth);
         offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
         break;
       }
 
       case JSOP_SWAP: {
         MOZ_ASSERT(ndefs == 2);
-        uint32_t tmp = offsetStack[stackDepth + 1];
+        OffsetAndDefIndex tmp = offsetStack[stackDepth + 1];
         offsetStack[stackDepth + 1] = offsetStack[stackDepth];
         offsetStack[stackDepth] = tmp;
         break;
       }
 
       case JSOP_PICK: {
         jsbytecode* pc = script_->offsetToPC(offset);
         unsigned n = GET_UINT8(pc);
         MOZ_ASSERT(ndefs == n + 1);
         uint32_t top = stackDepth + n;
-        uint32_t tmp = offsetStack[stackDepth];
+        OffsetAndDefIndex tmp = offsetStack[stackDepth];
         for (uint32_t i = stackDepth; i < top; i++)
             offsetStack[i] = offsetStack[i + 1];
         offsetStack[top] = tmp;
         break;
       }
 
       case JSOP_UNPICK: {
         jsbytecode* pc = script_->offsetToPC(offset);
         unsigned n = GET_UINT8(pc);
         MOZ_ASSERT(ndefs == n + 1);
         uint32_t top = stackDepth + n;
-        uint32_t tmp = offsetStack[top];
+        OffsetAndDefIndex tmp = offsetStack[top];
         for (uint32_t i = top; i > stackDepth; i--)
             offsetStack[i] = offsetStack[i - 1];
         offsetStack[stackDepth] = tmp;
         break;
       }
 
       case JSOP_AND:
       case JSOP_CHECKISOBJ:
@@ -533,50 +782,75 @@ BytecodeParser::simulateOp(JSOp op, uint
 
       case JSOP_SETELEM_SUPER:
       case JSOP_STRICTSETELEM_SUPER:
         // Keep the top value, removing other 3 values.
         MOZ_ASSERT(nuses == 4);
         MOZ_ASSERT(ndefs == 1);
         offsetStack[stackDepth] = offsetStack[stackDepth + 3];
         break;
+
+      case JSOP_ISGENCLOSING:
+      case JSOP_ISNOITER:
+      case JSOP_MOREITER:
+      case JSOP_OPTIMIZE_SPREADCALL:
+        // Keep the top value and push one more value.
+        MOZ_ASSERT(nuses == 1);
+        MOZ_ASSERT(ndefs == 2);
+        offsetStack[stackDepth + 1].set(offset, 1);
+        break;
     }
+
+#ifdef DEBUG
+  end:
+#endif /* DEBUG */
+
     stackDepth += ndefs;
     return stackDepth;
 }
 
 bool
-BytecodeParser::recordBytecode(uint32_t offset, const uint32_t* offsetStack,
+BytecodeParser::recordBytecode(uint32_t offset, const OffsetAndDefIndex* offsetStack,
                                uint32_t stackDepth)
 {
     MOZ_ASSERT(offset < script_->length());
 
     Bytecode*& code = codeArray_[offset];
     if (!code) {
-        code = alloc().new_<Bytecode>();
+        code = alloc().new_<Bytecode>(alloc());
         if (!code ||
             !code->captureOffsetStack(alloc(), offsetStack, stackDepth))
         {
             reportOOM();
             return false;
         }
     } else {
         code->mergeOffsetStack(offsetStack, stackDepth);
     }
 
     return true;
 }
 
 bool
 BytecodeParser::addJump(uint32_t offset, uint32_t* currentOffset,
-                        uint32_t stackDepth, const uint32_t* offsetStack)
+                        uint32_t stackDepth, const OffsetAndDefIndex* offsetStack,
+                        jsbytecode* pc, JumpKind kind)
 {
     if (!recordBytecode(offset, offsetStack, stackDepth))
         return false;
 
+#ifdef DEBUG
+    if (isStackDump) {
+        if (!codeArray_[offset]->addJump(script_->pcToOffset(pc), kind)) {
+            reportOOM();
+            return false;
+        }
+    }
+#endif /* DEBUG */
+
     Bytecode*& code = codeArray_[offset];
     if (offset < *currentOffset && !code->parsed) {
         // Backedge in a while/for loop, whose body has not been parsed due
         // to a lack of fallthrough at the loop head. Roll back the offset
         // to analyze the body.
         *currentOffset = offset;
     }
 
@@ -594,24 +868,24 @@ BytecodeParser::parse()
     if (!codeArray_) {
         reportOOM();
         return false;
     }
 
     mozilla::PodZero(codeArray_, length);
 
     // Fill in stack depth and definitions at initial bytecode.
-    Bytecode* startcode = alloc().new_<Bytecode>();
+    Bytecode* startcode = alloc().new_<Bytecode>(alloc());
     if (!startcode) {
         reportOOM();
         return false;
     }
 
     // Fill in stack depth and definitions at initial bytecode.
-    uint32_t* offsetStack = alloc().newArray<uint32_t>(maximumStackDepth());
+    OffsetAndDefIndex* offsetStack = alloc().newArray<OffsetAndDefIndex>(maximumStackDepth());
     if (maximumStackDepth() && !offsetStack) {
         reportOOM();
         return false;
     }
 
     startcode->stackDepth = 0;
     codeArray_[0] = startcode;
 
@@ -649,33 +923,48 @@ BytecodeParser::parse()
             // No need to reparse.
             continue;
         }
 
         code->parsed = true;
 
         uint32_t stackDepth = simulateOp(op, offset, offsetStack, code->stackDepth);
 
+#ifdef DEBUG
+        if (isStackDump) {
+            if (!code->captureOffsetStackAfter(alloc(), offsetStack, stackDepth)) {
+                reportOOM();
+                return false;
+            }
+        }
+#endif /* DEBUG */
+
         switch (op) {
           case JSOP_TABLESWITCH: {
             uint32_t defaultOffset = offset + GET_JUMP_OFFSET(pc);
             jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
             int32_t low = GET_JUMP_OFFSET(pc2);
             pc2 += JUMP_OFFSET_LEN;
             int32_t high = GET_JUMP_OFFSET(pc2);
             pc2 += JUMP_OFFSET_LEN;
 
-            if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack))
+            if (!addJump(defaultOffset, &nextOffset, stackDepth, offsetStack,
+                         pc, JumpKind::SwitchDefault))
+            {
                 return false;
+            }
 
             for (int32_t i = low; i <= high; i++) {
                 uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc2);
                 if (targetOffset != offset) {
-                    if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack))
+                    if (!addJump(targetOffset, &nextOffset, stackDepth, offsetStack,
+                                 pc, JumpKind::SwitchCase))
+                    {
                         return false;
+                    }
                 }
                 pc2 += JUMP_OFFSET_LEN;
             }
             break;
           }
 
           case JSOP_TRY: {
             // Everything between a try and corresponding catch or finally is conditional.
@@ -683,19 +972,28 @@ BytecodeParser::parse()
             // 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.
             JSTryNote* tn = script_->trynotes()->vector;
             JSTryNote* tnlimit = tn + script_->trynotes()->length;
             for (; tn < tnlimit; tn++) {
                 uint32_t startOffset = script_->mainOffset() + tn->start;
                 if (startOffset == offset + 1) {
                     uint32_t catchOffset = startOffset + tn->length;
-                    if (tn->kind == JSTRY_CATCH || tn->kind == JSTRY_FINALLY) {
-                        if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack))
+                    if (tn->kind == JSTRY_CATCH) {
+                        if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
+                                     pc, JumpKind::TryCatch))
+                        {
                             return false;
+                        }
+                    } else if (tn->kind == JSTRY_FINALLY) {
+                        if (!addJump(catchOffset, &nextOffset, stackDepth, offsetStack,
+                                     pc, JumpKind::TryFinally))
+                        {
+                            return false;
+                        }
                     }
                 }
             }
             break;
           }
 
           default:
             break;
@@ -704,17 +1002,18 @@ BytecodeParser::parse()
         // Check basic jump opcodes, which may or may not have a fallthrough.
         if (IsJumpOpcode(op)) {
             // Case instructions do not push the lvalue back when branching.
             uint32_t newStackDepth = stackDepth;
             if (op == JSOP_CASE)
                 newStackDepth--;
 
             uint32_t targetOffset = offset + GET_JUMP_OFFSET(pc);
-            if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack))
+            if (!addJump(targetOffset, &nextOffset, newStackDepth, offsetStack,
+                         pc, JumpKind::Simple))
                 return false;
         }
 
         // Handle any fallthrough from this opcode.
         if (BytecodeFallsThrough(op)) {
             if (!recordBytecode(successorOffset, offsetStack, stackDepth))
                 return false;
         }
@@ -735,32 +1034,36 @@ js::ReconstructStackDepth(JSContext* cx,
     *reachablePC = parser.isReachable(pc);
 
     if (*reachablePC)
         *depth = parser.stackDepthAtPC(pc);
 
     return true;
 }
 
+static unsigned
+Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
+             unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp);
+
 /*
  * If pc != nullptr, include a prefix indicating whether the PC is at the
  * current line. If showAll is true, include the source note type and the
  * entry stack depth.
  */
 static MOZ_MUST_USE bool
 DisassembleAtPC(JSContext* cx, JSScript* scriptArg, bool lines,
                 jsbytecode* pc, bool showAll, Sprinter* sp)
 {
     RootedScript script(cx, scriptArg);
     BytecodeParser parser(cx, script);
+    parser.setStackDump();
+    if (!parser.parse())
+        return false;
 
     if (showAll) {
-        if (!parser.parse())
-            return false;
-
         if (!sp->jsprintf("%s:%u\n", script->filename(), unsigned(script->lineno())))
             return false;
     }
 
     if (pc != nullptr) {
         if (sp->put("    ") < 0)
             return false;
     }
@@ -825,17 +1128,18 @@ DisassembleAtPC(JSContext* cx, JSScript*
             if (parser.isReachable(next)) {
                 if (!sp->jsprintf("%05u ", parser.stackDepthAtPC(next)))
                     return false;
             } else {
                 if (sp->put("      ") < 0)
                     return false;
             }
         }
-        unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp);
+        unsigned len = Disassemble1(cx, script, next, script->pcToOffset(next), lines,
+                                    &parser, sp);
         if (!len)
             return false;
 
         next += len;
     }
 
     return true;
 }
@@ -994,20 +1298,127 @@ ToDisassemblySource(JSContext* cx, Handl
         ReportOutOfMemory(cx);
         return false;
     }
 
     bytes->initBytes(source);
     return true;
 }
 
-unsigned
-js::Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
-                 unsigned loc, bool lines, Sprinter* sp)
+static bool
+DumpJumpOrigins(JSContext* cx, HandleScript script, jsbytecode* pc,
+                BytecodeParser* parser, Sprinter* sp)
 {
+    bool called = false;
+    auto callback = [&script, &sp, &called](jsbytecode* pc, BytecodeParser::JumpKind kind) {
+        if (!called) {
+            called = true;
+            if (!sp->put("\n# "))
+                return false;
+        } else {
+            if (!sp->put(", "))
+                return false;
+        }
+
+        switch (kind) {
+          case BytecodeParser::JumpKind::Simple:
+          break;
+
+          case BytecodeParser::JumpKind::SwitchCase:
+          if (!sp->put("switch-case "))
+              return false;
+          break;
+
+          case BytecodeParser::JumpKind::SwitchDefault:
+          if (!sp->put("swtich-default "))
+              return false;
+          break;
+
+          case BytecodeParser::JumpKind::TryCatch:
+          if (!sp->put("try-catch "))
+              return false;
+          break;
+
+          case BytecodeParser::JumpKind::TryFinally:
+          if (!sp->put("try-finally "))
+              return false;
+          break;
+        }
+
+        if (!sp->jsprintf("from %s @ %05u", CodeName[*pc], unsigned(script->pcToOffset(pc))))
+            return false;
+
+        return true;
+    };
+    if (!parser->forEachJumpOrigins(pc, callback))
+        return false;
+    if (called) {
+        if (!sp->put("\n"))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
+                          const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp);
+
+static unsigned
+Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
+             unsigned loc, bool lines, BytecodeParser* parser, Sprinter* sp)
+{
+    if (parser && parser->isReachable(pc)) {
+        if (!DumpJumpOrigins(cx, script, pc, parser, sp))
+            return 0;
+    }
+
+    size_t before = sp->stringEnd() - sp->string();
+    bool stackDumped = false;
+    auto dumpStack = [&cx, &script, &pc, &parser, &sp, &before, &stackDumped]() {
+        if (!parser)
+            return true;
+        if (stackDumped)
+            return true;
+        stackDumped = true;
+
+        size_t after = sp->stringEnd() - sp->string();
+        MOZ_ASSERT(after >= before);
+
+        static const size_t stack_column = 40;
+        for (size_t i = after - before; i < stack_column - 1; i++)
+            sp->put(" ");
+
+        sp->put(" # ");
+
+        if (!parser->isReachable(pc)) {
+            if (sp->put("!!! UNREACHABLE !!!") < 0)
+                return false;
+        } else {
+            uint32_t depth = parser->stackDepthAfterPC(pc);
+
+            for (uint32_t i = 0; i < depth; i++) {
+                if (i) {
+                    if (sp->put(" ") < 0)
+                        return false;
+                }
+
+                const OffsetAndDefIndex& offsetAndDefIndex
+                    = parser->offsetForStackOperandAfterPC(script->pcToOffset(pc), i);
+                // This will decompile the stack for the same PC many times.
+                // We'll avoid optimizing it since this is a testing function
+                // and it won't be worth managing cached expression here.
+                if (!DecompileAtPCForStackDump(cx, script, offsetAndDefIndex, sp))
+                    return false;
+            }
+        }
+
+        return true;
+    };
+
     JSOp op = (JSOp)*pc;
     if (op >= JSOP_LIMIT) {
         char numBuf1[12], numBuf2[12];
         SprintfLiteral(numBuf1, "%d", op);
         SprintfLiteral(numBuf2, "%d", JSOP_LIMIT);
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BYTECODE_TOO_BIG,
                                   numBuf1, numBuf2);
         return 0;
@@ -1136,16 +1547,21 @@ js::Disassemble1(JSContext* cx, HandleSc
         ptrdiff_t off = GET_JUMP_OFFSET(pc);
         jsbytecode* pc2 = pc + JUMP_OFFSET_LEN;
         low = GET_JUMP_OFFSET(pc2);
         pc2 += JUMP_OFFSET_LEN;
         high = GET_JUMP_OFFSET(pc2);
         pc2 += JUMP_OFFSET_LEN;
         if (!sp->jsprintf(" defaultOffset %d low %d high %d", int(off), low, high))
             return 0;
+
+        // Display stack dump before diplaying the offsets for each case.
+        if (!dumpStack())
+            return 0;
+
         for (i = low; i <= high; i++) {
             off = GET_JUMP_OFFSET(pc2);
             if (!sp->jsprintf("\n\t%d: %d", i, int(off)))
                 return 0;
             pc2 += JUMP_OFFSET_LEN;
         }
         len = 1 + pc2 - pc;
         break;
@@ -1193,20 +1609,31 @@ js::Disassemble1(JSContext* cx, HandleSc
 
       default: {
         char numBuf[12];
         SprintfLiteral(numBuf, "%x", cs->format);
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNKNOWN_FORMAT, numBuf);
         return 0;
       }
     }
+
+    if (!dumpStack())
+        return 0;
+
     sp->put("\n");
     return len;
 }
 
+unsigned
+js::Disassemble1(JSContext* cx, JS::Handle<JSScript*> script, jsbytecode* pc, unsigned loc,
+                 bool lines, Sprinter* sp)
+{
+    return Disassemble1(cx, script, pc, loc, lines, nullptr, sp);
+}
+
 #endif /* DEBUG */
 
 namespace {
 /*
  * The expression decompiler is invoked by error handling code to produce a
  * string representation of the erroring expression. As it's only a debugging
  * tool, it only supports basic expressions. For anything complicated, it simply
  * puts "(intermediate value)" into the error result.
@@ -1238,50 +1665,68 @@ namespace {
  */
 struct ExpressionDecompiler
 {
     JSContext* cx;
     RootedScript script;
     BytecodeParser parser;
     Sprinter sprinter;
 
+#ifdef DEBUG
+    // Dedicated mode for stack dump.
+    // Generates an expression for stack dump, including internal state,
+    // and also disables special handling for self-hosted code.
+    bool isStackDump;
+#endif /* DEBUG */
+
     ExpressionDecompiler(JSContext* cx, JSScript* script)
         : cx(cx),
           script(cx, script),
           parser(cx, script),
           sprinter(cx)
+#ifdef DEBUG
+          ,
+          isStackDump(false)
+#endif /* DEBUG */
     {}
     bool init();
     bool decompilePCForStackOperand(jsbytecode* pc, int i);
-    bool decompilePC(jsbytecode* pc);
+    bool decompilePC(jsbytecode* pc, uint8_t defIndex);
+    bool decompilePC(const OffsetAndDefIndex& offsetAndDefIndex);
     JSAtom* getArg(unsigned slot);
     JSAtom* loadAtom(jsbytecode* pc);
     bool quote(JSString* s, uint32_t quote);
     bool write(const char* s);
     bool write(JSString* str);
     bool getOutput(char** out);
+#ifdef DEBUG
+    void setStackDump() {
+        isStackDump = true;
+        parser.setStackDump();
+    }
+#endif /* DEBUG */
 };
 
 bool
 ExpressionDecompiler::decompilePCForStackOperand(jsbytecode* pc, int i)
 {
-    pc = parser.pcForStackOperand(pc, i);
-    if (!pc)
-        return write("(intermediate value)");
-    return decompilePC(pc);
+    return decompilePC(parser.offsetForStackOperand(script->pcToOffset(pc), i));
 }
 
 bool
-ExpressionDecompiler::decompilePC(jsbytecode* pc)
+ExpressionDecompiler::decompilePC(jsbytecode* pc, uint8_t defIndex)
 {
     MOZ_ASSERT(script->containsPC(pc));
 
     JSOp op = (JSOp)*pc;
 
     if (const char* token = CodeToken[op]) {
+        MOZ_ASSERT(defIndex == 0);
+        MOZ_ASSERT(CodeSpec[op].ndefs == 1);
+
         // Handle simple cases of binary and unary operators.
         switch (CodeSpec[op].nuses) {
           case 2: {
             jssrcnote* sn = GetSrcNote(cx, script, pc);
             if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP)
                 return write("(") &&
                        decompilePCForStackOperand(pc, -2) &&
                        write(" ") &&
@@ -1312,17 +1757,23 @@ ExpressionDecompiler::decompilePC(jsbyte
       case JSOP_GETINTRINSIC:
         return write(loadAtom(pc));
       case JSOP_GETARG: {
         unsigned slot = GET_ARGNO(pc);
 
         // For self-hosted scripts that are called from non-self-hosted code,
         // decompiling the parameter name in the self-hosted script is
         // unhelpful. Decompile the argument name instead.
-        if (script->selfHosted()) {
+        if (script->selfHosted()
+#ifdef DEBUG
+            // For stack dump, argument name is not necessary.
+            && !isStackDump
+#endif /* DEBUG */
+            )
+        {
             char* result;
             if (!DecompileArgumentFromStack(cx, slot, &result))
                 return false;
 
             // Note that decompiling the argument in the parent frame might
             // not succeed.
             if (result) {
 		bool ok = write(result);
@@ -1477,20 +1928,197 @@ ExpressionDecompiler::decompilePC(jsbyte
       case JSOP_TYPEOF:
       case JSOP_TYPEOFEXPR:
         return write("(typeof ") &&
                decompilePCForStackOperand(pc, -1) &&
                write(")");
       default:
         break;
     }
+
+#ifdef DEBUG
+    if (isStackDump) {
+        // Special decompilation for stack dump.
+        switch (op) {
+          case JSOP_ARGUMENTS:
+            return write("arguments");
+
+          case JSOP_BINDGNAME:
+            return write("GLOBAL");
+
+          case JSOP_BINDNAME:
+          case JSOP_BINDVAR:
+            return write("ENV");
+
+          case JSOP_CALLEE:
+            return write("CALLEE");
+
+          case JSOP_CALLSITEOBJ:
+            return write("OBJ");
+
+          case JSOP_CLASSCONSTRUCTOR:
+          case JSOP_DERIVEDCONSTRUCTOR:
+            return write("CONSTRUCTOR");
+
+          case JSOP_CLASSHERITAGE:
+            if (defIndex == 0)
+                return write("FUNCPROTO");
+            MOZ_ASSERT(defIndex == 1);
+            return write("OBJPROTO");
+
+          case JSOP_DOUBLE:
+            return sprinter.printf("%lf", script->getConst(GET_UINT32_INDEX(pc)).toDouble()) >= 0;
+
+          case JSOP_EXCEPTION:
+            return write("EXCEPTION");
+
+          case JSOP_FINALLY:
+            if (defIndex == 0)
+                return write("THROWING");
+            MOZ_ASSERT(defIndex == 1);
+            return write("PC");
+
+          case JSOP_GIMPLICITTHIS:
+          case JSOP_FUNCTIONTHIS:
+          case JSOP_IMPLICITTHIS:
+            return write("THIS");
+
+          case JSOP_FUNWITHPROTO:
+            return write("FUN");
+
+          case JSOP_GENERATOR:
+            return write("GENERATOR");
+
+          case JSOP_GETIMPORT:
+            return write("VAL");
+
+          case JSOP_GETRVAL:
+            return write("RVAL");
+
+          case JSOP_HOLE:
+            return write("HOLE");
+
+          case JSOP_INITELEM_INC:
+            // For stack dump, defIndex == 0 is not used.
+            MOZ_ASSERT(defIndex == 1);
+            return write("INDEX");
+
+          case JSOP_ISGENCLOSING:
+            // For stack dump, defIndex == 0 is not used.
+            MOZ_ASSERT(defIndex == 1);
+            return write("ISGENCLOSING");
+
+          case JSOP_ISNOITER:
+            // For stack dump, defIndex == 0 is not used.
+            MOZ_ASSERT(defIndex == 1);
+            return write("ISNOITER");
+
+          case JSOP_IS_CONSTRUCTING:
+            return write("JS_IS_CONSTRUCTING");
+
+          case JSOP_ITER:
+            return write("ITER");
+
+          case JSOP_LAMBDA:
+          case JSOP_LAMBDA_ARROW:
+          case JSOP_TOASYNC:
+            return write("FUN");
+
+          case JSOP_MOREITER:
+            // For stack dump, defIndex == 0 is not used.
+            MOZ_ASSERT(defIndex == 1);
+            return write("MOREITER");
+
+          case JSOP_MUTATEPROTO:
+            return write("SUCCEEDED");
+
+          case JSOP_NEWINIT:
+          case JSOP_NEWOBJECT:
+          case JSOP_OBJWITHPROTO:
+            return write("OBJ");
+
+          case JSOP_OPTIMIZE_SPREADCALL:
+            // For stack dump, defIndex == 0 is not used.
+            MOZ_ASSERT(defIndex == 1);
+            return write("OPTIMIZED");
+
+          case JSOP_REST:
+            return write("REST");
+
+          case JSOP_RESUME:
+            return write("RVAL");
+
+          case JSOP_SPREADCALLARRAY:
+            return write("[]");
+
+          case JSOP_SUPERBASE:
+            return write("HOMEOBJECTPROTO");
+
+          case JSOP_TOID:
+            return write("TOID(") &&
+                   decompilePCForStackOperand(pc, -1) &&
+                   write(")");
+          case JSOP_TOSTRING:
+            return write("TOSTRING(") &&
+                   decompilePCForStackOperand(pc, -1) &&
+                   write(")");
+
+          case JSOP_UNINITIALIZED:
+            return write("UNINITIALIZED");
+
+          case JSOP_YIELD:
+            // Printing "yield SOMETHING" is confusing since the operand doesn't
+            // match to the syntax, since the stack operand for "yield 10" is
+            // the result object, not 10.
+            return write("RVAL");
+
+          default:
+            break;
+        }
+        return write("<unknown>");
+    }
+#endif /* DEBUG */
+
     return write("(intermediate value)");
 }
 
 bool
+ExpressionDecompiler::decompilePC(const OffsetAndDefIndex& offsetAndDefIndex)
+{
+    if (offsetAndDefIndex.isSpecial()) {
+#ifdef DEBUG
+        if (isStackDump) {
+            if (offsetAndDefIndex.isMerged()) {
+                if (!write("merged<"))
+                    return false;
+            } else if (offsetAndDefIndex.isIgnored()) {
+                if (!write("ignored<"))
+                    return false;
+            }
+
+            if (!decompilePC(script->offsetToPC(offsetAndDefIndex.specialOffset()),
+                             offsetAndDefIndex.specialDefIndex()))
+            {
+                return false;
+            }
+
+            if (!write(">"))
+                return false;
+
+            return true;
+        }
+#endif /* DEBUG */
+        return write("(intermediate value)");
+    }
+
+    return decompilePC(script->offsetToPC(offsetAndDefIndex.offset()),
+                       offsetAndDefIndex.defIndex());
+}
+
+bool
 ExpressionDecompiler::init()
 {
     assertSameCompartment(cx, script);
 
     if (!sprinter.init())
         return false;
 
     if (!parser.parse())
@@ -1554,22 +2182,47 @@ ExpressionDecompiler::getOutput(char** r
         return false;
     js_memcpy(*res, sprinter.stringAt(0), len);
     (*res)[len] = 0;
     return true;
 }
 
 }  // anonymous namespace
 
+#ifdef DEBUG
+static bool
+DecompileAtPCForStackDump(JSContext* cx, HandleScript script,
+                          const OffsetAndDefIndex& offsetAndDefIndex, Sprinter* sp)
+{
+    ExpressionDecompiler ed(cx, script);
+    ed.setStackDump();
+    if (!ed.init())
+        return false;
+
+    if (!ed.decompilePC(offsetAndDefIndex))
+        return false;
+
+    char* result;
+    if (!ed.getOutput(&result))
+        return false;
+
+    if (!sp->put(result))
+        return false;
+
+    return true;
+}
+#endif /* DEBUG */
+
 static bool
 FindStartPC(JSContext* cx, const FrameIter& iter, int spindex, int skipStackHits, const Value& v,
-            jsbytecode** valuepc)
+            jsbytecode** valuepc, uint8_t* defIndex)
 {
     jsbytecode* current = *valuepc;
     *valuepc = nullptr;
+    *defIndex = 0;
 
     if (spindex == JSDVG_IGNORE_STACK)
         return true;
 
     /*
      * FIXME: Fall back if iter.isIon(), since the stack snapshot may be for the
      * previous pc (see bug 831120).
      */
@@ -1599,25 +2252,21 @@ FindStartPC(JSContext* cx, const FrameIt
         int stackHits = 0;
         Value s;
         do {
             if (!index)
                 return true;
             s = iter.frameSlotValue(--index);
         } while (s != v || stackHits++ != skipStackHits);
 
-        // If the current PC has fewer values on the stack than the index we are
-        // looking for, the blamed value must be one pushed by the current
-        // bytecode, so restore *valuepc.
-        if (index < size_t(parser.stackDepthAtPC(current)))
-            *valuepc = parser.pcForStackOperand(current, index);
-        else
-            *valuepc = current;
+        MOZ_ASSERT(index < size_t(parser.stackDepthAtPC(current)));
+
+        *valuepc = parser.pcForStackOperand(current, index, defIndex);
     } else {
-        *valuepc = parser.pcForStackOperand(current, spindex);
+        *valuepc = parser.pcForStackOperand(current, spindex, defIndex);
     }
     return true;
 }
 
 static bool
 DecompileExpressionFromStack(JSContext* cx, int spindex, int skipStackHits, HandleValue v, char** res)
 {
     MOZ_ASSERT(spindex < 0 ||
@@ -1644,25 +2293,26 @@ DecompileExpressionFromStack(JSContext* 
     jsbytecode* valuepc = frameIter.pc();
 
     MOZ_ASSERT(script->containsPC(valuepc));
 
     // Give up if in prologue.
     if (valuepc < script->main())
         return true;
 
-    if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc))
+    uint8_t defIndex;
+    if (!FindStartPC(cx, frameIter, spindex, skipStackHits, v, &valuepc, &defIndex))
         return false;
     if (!valuepc)
         return true;
 
     ExpressionDecompiler ed(cx, script);
     if (!ed.init())
         return false;
-    if (!ed.decompilePC(valuepc))
+    if (!ed.decompilePC(valuepc, defIndex))
         return false;
 
     return ed.getOutput(res);
 }
 
 UniqueChars
 js::DecompileValueGenerator(JSContext* cx, int spindex, HandleValue v,
                             HandleString fallbackArg, int skipStackHits)
@@ -2101,17 +2751,18 @@ GetPCCountJSON(JSContext* cx, const Scri
             if (!buf.append('\"'))
                 return false;
         }
 
         {
             ExpressionDecompiler ed(cx, script);
             if (!ed.init())
                 return false;
-            if (!ed.decompilePC(pc))
+            // defIndex passed here is not used.
+            if (!ed.decompilePC(pc, /* defIndex = */ 0))
                 return false;
             char* text;
             if (!ed.getOutput(&text))
                 return false;
             JSString* str = JS_NewStringCopyZ(cx, text);
             js_free(text);
             if (!AppendJSONProperty(buf, "text"))
                 return false;