Bug 1192786 - Hook ARM disassembler into IONFLAGS=codegen machinery. r=nbp
authorLars T Hansen <lhansen@mozilla.com>
Tue, 25 Aug 2015 08:09:09 +0200
changeset 291823 d7c9da5fe4770590418aa2e90562553b5c676c4f
parent 291822 81b4ff5388418014f6a4f51f6060d5d116b9905d
child 291824 92e8e1595ee5bdc3abe87d0cb280b4a8fd074736
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1192786
milestone43.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 1192786 - Hook ARM disassembler into IONFLAGS=codegen machinery. r=nbp
js/src/jit/Ion.cpp
js/src/jit/arm/Assembler-arm.cpp
js/src/jit/arm/Assembler-arm.h
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/arm64/MacroAssembler-arm64.h
js/src/jit/mips32/MacroAssembler-mips32.cpp
js/src/jit/mips32/MacroAssembler-mips32.h
js/src/jit/none/MacroAssembler-none.h
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jit/x64/MacroAssembler-x64.h
js/src/jit/x86/MacroAssembler-x86.h
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -224,16 +224,17 @@ JitRuntime::initialize(JSContext* cx)
         if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
             return false;
 
         for (uint32_t id = 0;; id++) {
             FrameSizeClass class_ = FrameSizeClass::FromClass(id);
             if (class_ == FrameSizeClass::ClassLimit())
                 break;
             bailoutTables_.infallibleAppend((JitCode*)nullptr);
+            JitSpew(JitSpew_Codegen, "# Bailout table");
             bailoutTables_[id] = generateBailoutTable(cx, id);
             if (!bailoutTables_[id])
                 return false;
         }
 
         JitSpew(JitSpew_Codegen, "# Emitting bailout handler");
         bailoutHandler_ = generateBailoutHandler(cx);
         if (!bailoutHandler_)
@@ -292,16 +293,17 @@ JitRuntime::initialize(JSContext* cx)
 
     JitSpew(JitSpew_Codegen, "# Emitting free stub");
     freeStub_ = generateFreeStub(cx);
     if (!freeStub_)
         return false;
 
     JitSpew(JitSpew_Codegen, "# Emitting VM function wrappers");
     for (VMFunction* fun = VMFunction::functions; fun; fun = fun->next) {
+        JitSpew(JitSpew_Codegen, "# VM function wrapper");
         if (!generateVMWrapper(cx, *fun))
             return false;
     }
 
     JitSpew(JitSpew_Codegen, "# Emitting lazy link stub");
     lazyLinkStub_ = generateLazyLinkStub(cx);
     if (!lazyLinkStub_)
         return false;
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -5,19 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/arm/Assembler-arm.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include "jscompartment.h"
+#ifdef JS_DISASM_ARM
+#include "jsprf.h"
+#endif
 #include "jsutil.h"
 
 #include "gc/Marking.h"
+#include "jit/arm/disasm/Disasm-arm.h"
 #include "jit/arm/MacroAssembler-arm.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/JitCompartment.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::CountLeadingZeroes32;
@@ -1400,28 +1404,263 @@ size_t
 Assembler::bytesNeeded() const
 {
     return size() +
         jumpRelocationTableBytes() +
         dataRelocationTableBytes() +
         preBarrierTableBytes();
 }
 
+#ifdef JS_DISASM_ARM
+
+// Labels are named as they are encountered by adding names to a
+// table, using the Label address as the key.  This is made tricky by
+// the (memory for) Label objects being reused, but reused label
+// objects are recognizable from being marked as not used or not
+// bound.  See spewResolve().
+//
+// In a number of cases there is no information about the target, and
+// we just end up printing "patchable constant load to PC".  This is
+// true especially for jumps to bailout handlers (which have no
+// names).  See spewData() and its callers.  In some cases (loop back
+// edges) some information about the intended target may be propagated
+// from higher levels, and if so it's printed here.
+
+void
+Assembler::spew(Instruction* i)
+{
+    if (spewDisabled())
+        return;
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
+    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
+    dasm.InstructionDecode(buffer, loc);
+    spew("   %08x  %s", reinterpret_cast<uint32_t>(loc), buffer.start());
+}
+
+void
+Assembler::spewTarget(Label* target)
+{
+    if (spewDisabled())
+        return;
+    spew("                        -> %d%s", spewResolve(target), !target->bound() ? "f" : "");
+}
+
+// If a target label is known, always print that and do not attempt to
+// disassemble the branch operands, as they will often be encoding
+// metainformation (pointers for a chain of jump instructions), and
+// not actual branch targets.
+
+void
+Assembler::spewBranch(Instruction* i, Label* target /* may be nullptr */)
+{
+    if (spewDisabled())
+        return;
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
+    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
+    dasm.InstructionDecode(buffer, loc);
+    char labelBuf[128];
+    labelBuf[0] = 0;
+    if (!target)
+        JS_snprintf(labelBuf, sizeof(labelBuf), "  -> (link-time target)");
+    if (InstBranchImm::IsTHIS(*i)) {
+        InstBranchImm* bimm = InstBranchImm::AsTHIS(*i);
+        BOffImm destOff;
+        bimm->extractImm(&destOff);
+        if (destOff.isInvalid() || target) {
+            // The target information in the instruction is likely garbage, so remove it.
+            // The target label will in any case be printed if we have it.
+            //
+            // The format of the instruction disassembly is [0-9a-f]{8}\s+\S+\s+.*,
+            // where the \S+ string is the opcode.  Strip everything after the opcode,
+            // and attach the label if we have it.
+            int i;
+            for ( i=8 ; i < buffer.length() && buffer[i] == ' ' ; i++ )
+                ;
+            for ( ; i < buffer.length() && buffer[i] != ' ' ; i++ )
+                ;
+            buffer[i] = 0;
+            if (target) {
+                JS_snprintf(labelBuf, sizeof(labelBuf), "  -> %d%s", spewResolve(target),
+                            !target->bound() ? "f" : "");
+                target = nullptr;
+            }
+        }
+    }
+    spew("   %08x  %s%s", reinterpret_cast<uint32_t>(loc), buffer.start(), labelBuf);
+    if (target)
+        spewTarget(target);
+}
+
+void
+Assembler::spewLabel(Label* l)
+{
+    if (spewDisabled())
+        return;
+    spew("                        %d:", spewResolve(l));
+}
+
+void
+Assembler::spewRetarget(Label* label, Label* target)
+{
+    if (spewDisabled())
+        return;
+    spew("                        %d: .retarget -> %d%s",
+         spewResolve(label), spewResolve(target), !target->bound() ? "f" : "");
+}
+
+void
+Assembler::spewData(BufferOffset addr, size_t numInstr, bool loadToPC)
+{
+    if (spewDisabled())
+        return;
+    uint32_t *instr = reinterpret_cast<uint32_t*>(m_buffer.getInst(addr));
+    for ( size_t k=0 ; k < numInstr ; k++ ) {
+        spew("   %08x  %08x       (patchable constant load%s)",
+             reinterpret_cast<uint32_t>(instr+k), *(instr+k), loadToPC ? " to PC" : "");
+    }
+}
+
+bool
+Assembler::spewDisabled()
+{
+    return !JitSpewEnabled(JitSpew_Codegen);
+}
+
+void
+Assembler::spew(const char* fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    spew(fmt, args);
+    va_end(args);
+}
+
+void
+Assembler::spew(const char* fmt, va_list va)
+{
+    js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va);
+}
+
+uint32_t
+Assembler::spewResolve(Label* l)
+{
+    // Note, spewResolve will sometimes return 0 when it is triggered
+    // by the profiler and not by a full disassembly, since in that
+    // case a label can be used or bound but not previously have been
+    // defined.
+    return l->used() || l->bound() ? spewProbe(l) : spewDefine(l);
+}
+
+uint32_t
+Assembler::spewProbe(Label* l)
+{
+    uint32_t key = reinterpret_cast<uint32_t>(l);
+    uint32_t value = 0;
+    spewNodes_.lookup(key, &value);
+    return value;
+}
+
+uint32_t
+Assembler::spewDefine(Label* l)
+{
+    uint32_t key = reinterpret_cast<uint32_t>(l);
+    spewNodes_.remove(key);
+    uint32_t value = spewNext_++;
+    if (!spewNodes_.add(key, value))
+        return 0;
+    return value;
+}
+
+Assembler::SpewNodes::~SpewNodes()
+{
+    Node* p = nodes;
+    while (p) {
+        Node* victim = p;
+        p = p->next;
+        js_free(victim);
+    }
+}
+
+bool
+Assembler::SpewNodes::lookup(uint32_t key, uint32_t* value)
+{
+    for ( Node* p = nodes ; p ; p = p->next ) {
+        if (p->key == key) {
+            *value = p->value;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+Assembler::SpewNodes::add(uint32_t key, uint32_t value)
+{
+    Node* node = (Node*)js_malloc(sizeof(Node));
+    if (!node)
+        return false;
+    node->key = key;
+    node->value = value;
+    node->next = nodes;
+    nodes = node;
+    return true;
+}
+
+bool
+Assembler::SpewNodes::remove(uint32_t key)
+{
+    for ( Node* p = nodes, *pp = nullptr ; p ; pp = p, p = p->next ) {
+        if (p->key == key) {
+            if (pp)
+                pp->next = p->next;
+            else
+                nodes = p->next;
+            js_free(p);
+            return true;
+        }
+    }
+    return false;
+}
+
+#endif // JS_DISASM_ARM
+
 // Write a blob of binary into the instruction stream.
 BufferOffset
 Assembler::writeInst(uint32_t x)
 {
-    return m_buffer.putInt(x);
+    BufferOffset offs = m_buffer.putInt(x);
+#ifdef JS_DISASM_ARM
+    spew(m_buffer.getInst(offs));
+#endif
+    return offs;
 }
 
 BufferOffset
-Assembler::writeBranchInst(uint32_t x)
+Assembler::writeBranchInst(uint32_t x, Label* documentation)
 {
-    return m_buffer.putInt(x, /* markAsBranch = */ true);
+    BufferOffset offs = m_buffer.putInt(x, /* markAsBranch = */ true);
+#ifdef JS_DISASM_ARM
+    spewBranch(m_buffer.getInst(offs), documentation);
+#endif
+    return offs;
 }
+
+// Allocate memory for a branch instruction, it will be overwritten
+// subsequently and should not be disassembled.
+
+BufferOffset
+Assembler::allocBranchInst()
+{
+    return m_buffer.putInt(Always | InstNOP::NopInst, /* markAsBranch = */ true);
+}
+
 void
 Assembler::WriteInstStatic(uint32_t x, uint32_t* dest)
 {
     MOZ_ASSERT(dest != nullptr);
     *dest = x;
 }
 
 void
@@ -1862,22 +2101,44 @@ Assembler::as_extdtr(LoadStore ls, int s
 
 BufferOffset
 Assembler::as_dtm(LoadStore ls, Register rn, uint32_t mask,
                 DTMMode mode, DTMWriteBack wb, Condition c)
 {
     return writeInst(0x08000000 | RN(rn) | ls | mode | mask | c | wb);
 }
 
+// Note, it's possible for markAsBranch and loadToPC to disagree,
+// because some loads to the PC are not necessarily encoding
+// instructions that should be marked as branches: only patchable
+// near branch instructions should be marked.
+
+BufferOffset
+Assembler::allocEntry(size_t numInst, unsigned numPoolEntries,
+                      uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe,
+                      bool markAsBranch, bool loadToPC)
+{
+    BufferOffset offs = m_buffer.allocEntry(numInst, numPoolEntries, inst, data, pe, markAsBranch);
+#ifdef JS_DISASM_ARM
+    spewData(offs, numInst, loadToPC);
+#endif
+    return offs;
+}
+
+// This is also used for instructions that might be resolved into branches,
+// or might not.  If dest==pc then it is effectively a branch.
+
 BufferOffset
 Assembler::as_Imm32Pool(Register dest, uint32_t value, Condition c)
 {
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolDTR, dest);
-    return m_buffer.allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value);
+    BufferOffset offs = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, nullptr, false,
+                                   dest == pc);
+    return offs;
 }
 
 /* static */ void
 Assembler::WritePoolEntry(Instruction* addr, Condition c, uint32_t data)
 {
     MOZ_ASSERT(addr->is<InstLDR>());
     int32_t offset = addr->encode() & 0xfff;
     if ((addr->encode() & IsUp) != IsUp)
@@ -1886,52 +2147,57 @@ Assembler::WritePoolEntry(Instruction* a
     uint32_t * dest = reinterpret_cast<uint32_t*>(&rawAddr[offset + 8]);
     *dest = data;
     Condition orig_cond;
     addr->extractCond(&orig_cond);
     MOZ_ASSERT(orig_cond == c);
 }
 
 BufferOffset
-Assembler::as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c)
+Assembler::as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c,
+                         Label* documentation)
 {
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolBranch, pc);
-    BufferOffset ret = m_buffer.allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe,
-                                           /* markAsBranch = */ true);
+    BufferOffset ret = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe,
+                                  /* markAsBranch = */ true, /* loadToPC = */ true);
     // If this label is already bound, then immediately replace the stub load
     // with a correct branch.
     if (label->bound()) {
         BufferOffset dest(label);
         as_b(dest.diffB<BOffImm>(ret), c, ret);
     } else {
         label->use(ret.getOffset());
     }
+#ifdef JS_DISASM_ARM
+    if (documentation)
+        spewTarget(documentation);
+#endif
     return ret;
 }
 
 BufferOffset
 Assembler::as_FImm64Pool(VFPRegister dest, double value, Condition c)
 {
     MOZ_ASSERT(dest.isDouble());
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolVDTR, dest);
-    return m_buffer.allocEntry(1, 2, (uint8_t*)&php.raw, (uint8_t*)&value);
+    return allocEntry(1, 2, (uint8_t*)&php.raw, (uint8_t*)&value);
 }
 
 BufferOffset
 Assembler::as_FImm32Pool(VFPRegister dest, float value, Condition c)
 {
     // Insert floats into the double pool as they have the same limitations on
     // immediate offset. This wastes 4 bytes padding per float. An alternative
     // would be to have a separate pool for floats.
     MOZ_ASSERT(dest.isSingle());
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolVDTR, dest);
-    return m_buffer.allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value);
+    return allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value);
 }
 
 // Pool callbacks stuff:
 void
 Assembler::InsertIndexIntoTag(uint8_t* load_, uint32_t index)
 {
     uint32_t* load = (uint32_t*)load_;
     PoolHintPun php;
@@ -2081,61 +2347,66 @@ Assembler::WritePoolGuard(BufferOffset b
 {
     BOffImm off = afterPool.diffB<BOffImm>(branch);
     *dest = InstBImm(off, Always);
 }
 
 // Branch can branch to an immediate *or* to a register.
 // Branches to immediates are pc relative, branches to registers are absolute.
 BufferOffset
-Assembler::as_b(BOffImm off, Condition c)
+Assembler::as_b(BOffImm off, Condition c, Label* documentation)
 {
-    BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode());
+    BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode(), documentation);
     return ret;
 }
 
 BufferOffset
 Assembler::as_b(Label* l, Condition c)
 {
     if (m_buffer.oom()) {
         BufferOffset ret;
         return ret;
     }
 
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
-        BufferOffset ret = writeBranchInst(Always | InstNOP::NopInst);
+        BufferOffset ret = allocBranchInst();
         as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
+#ifdef JS_DISASM_ARM
+        spewBranch(m_buffer.getInst(ret), l);
+#endif
         return ret;
     }
 
     int32_t old;
     BufferOffset ret;
     if (l->used()) {
         old = l->offset();
         // This will currently throw an assertion if we couldn't actually
         // encode the offset of the branch.
         if (!BOffImm::IsInRange(old)) {
             m_buffer.fail_bail();
             return ret;
         }
-        ret = as_b(BOffImm(old), c);
+        ret = as_b(BOffImm(old), c, l);
     } else {
         old = LabelBase::INVALID_OFFSET;
         BOffImm inv;
-        ret = as_b(inv, c);
+        ret = as_b(inv, c, l);
     }
     DebugOnly<int32_t> check = l->use(ret.getOffset());
     MOZ_ASSERT(check == old);
     return ret;
 }
 
 BufferOffset
 Assembler::as_b(BOffImm off, Condition c, BufferOffset inst)
 {
+    // JS_DISASM_ARM NOTE: Can't disassemble here, because numerous callers use this to
+    // patchup old code.  Must disassemble in caller where it makes sense.  Not many callers.
     *editSrc(inst) = InstBImm(off, c);
     return inst;
 }
 
 // blx can go to either an immediate or a register.
 // When blx'ing to a register, we change processor state depending on the low
 // bit of the register when blx'ing to an immediate, we *always* change
 // processor state.
@@ -2144,52 +2415,55 @@ BufferOffset
 Assembler::as_blx(Register r, Condition c)
 {
     return writeInst(((int) c) | OpBlx | r.code());
 }
 
 // bl can only branch to an pc-relative immediate offset
 // It cannot change the processor state.
 BufferOffset
-Assembler::as_bl(BOffImm off, Condition c)
+Assembler::as_bl(BOffImm off, Condition c, Label* documentation)
 {
-    return writeBranchInst(((int)c) | OpBl | off.encode());
+    return writeBranchInst(((int)c) | OpBl | off.encode(), documentation);
 }
 
 BufferOffset
 Assembler::as_bl(Label* l, Condition c)
 {
     if (m_buffer.oom()) {
         BufferOffset ret;
         return ret;
     }
 
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
-        BufferOffset ret = writeBranchInst(Always | InstNOP::NopInst);
+        BufferOffset ret = allocBranchInst();
         as_bl(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
+#ifdef JS_DISASM_ARM
+        spewBranch(m_buffer.getInst(ret), l);
+#endif
         return ret;
     }
 
     int32_t old;
     BufferOffset ret;
     // See if the list was empty :(
     if (l->used()) {
         // This will currently throw an assertion if we couldn't actually encode
         // the offset of the branch.
         old = l->offset();
         if (!BOffImm::IsInRange(old)) {
             m_buffer.fail_bail();
             return ret;
         }
-        ret = as_bl(BOffImm(old), c);
+        ret = as_bl(BOffImm(old), c, l);
     } else {
         old = LabelBase::INVALID_OFFSET;
         BOffImm inv;
-        ret = as_bl(inv, c);
+        ret = as_bl(inv, c, l);
     }
     DebugOnly<int32_t> check = l->use(ret.getOffset());
     MOZ_ASSERT(check == old);
     return ret;
 }
 
 BufferOffset
 Assembler::as_bl(BOffImm off, Condition c, BufferOffset inst)
@@ -2503,16 +2777,19 @@ Assembler::nextLink(BufferOffset b, Buff
     // BufferOffset into the space they provided.
     new (next) BufferOffset(destOff.decode());
     return true;
 }
 
 void
 Assembler::bind(Label* label, BufferOffset boff)
 {
+#ifdef JS_DISASM_ARM
+    spewLabel(label);
+#endif
     if (label->used()) {
         bool more;
         // If our caller didn't give us an explicit target to bind to then we
         // want to bind to the location of the next instruction.
         BufferOffset dest = boff.assigned() ? boff : nextOffset();
         BufferOffset b(label);
         do {
             BufferOffset next;
@@ -2530,16 +2807,19 @@ Assembler::bind(Label* label, BufferOffs
         } while (more);
     }
     label->bind(nextOffset().getOffset());
 }
 
 void
 Assembler::bind(RepatchLabel* label)
 {
+    // It does not seem to be useful to record this label for
+    // disassembly, as the value that is bound to the label is often
+    // effectively garbage and is replaced by something else later.
     BufferOffset dest = nextOffset();
     if (label->used()) {
         // If the label has a use, then change this use to refer to the bound
         // label.
         BufferOffset branchOff(label->offset());
         // Since this was created with a RepatchLabel, the value written in the
         // instruction stream is not branch shaped, it is PoolHintData shaped.
         Instruction* branch = editSrc(branchOff);
@@ -2553,16 +2833,19 @@ Assembler::bind(RepatchLabel* label)
         as_b(dest.diffB<BOffImm>(branchOff), cond, branchOff);
     }
     label->bind(dest.getOffset());
 }
 
 void
 Assembler::retarget(Label* label, Label* target)
 {
+#ifdef JS_DISASM_ARM
+    spewRetarget(label, target);
+#endif
     if (label->used()) {
         if (target->bound()) {
             bind(label, BufferOffset(target));
         } else if (target->used()) {
             // The target is not bound but used. Prepend label's branch list
             // onto target's.
             BufferOffset labelBranchOffset(label);
             BufferOffset next;
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1223,23 +1223,34 @@ class Assembler : public AssemblerShared
     // This should be protected, but since CodeGenerator wants to use it, it
     // needs to go out here :(
 
     BufferOffset nextOffset() {
         return m_buffer.nextOffset();
     }
 
   protected:
-    BufferOffset labelOffset (Label* l) {
-        return BufferOffset(l->bound());
-    }
+    // Shim around AssemblerBufferWithConstantPools::allocEntry.
+    BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
+                            uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe = nullptr,
+                            bool markAsBranch = false, bool loadToPC = false);
 
     Instruction* editSrc (BufferOffset bo) {
         return m_buffer.getInst(bo);
     }
+
+#ifdef JS_DISASM_ARM
+    void spew(Instruction* i);
+    void spewBranch(Instruction* i, Label* target);
+    void spewData(BufferOffset addr, size_t numInstr, bool loadToPC);
+    void spewLabel(Label* label);
+    void spewRetarget(Label* label, Label* target);
+    void spewTarget(Label* l);
+#endif
+
   public:
     void resetCounter();
     uint32_t actualOffset(uint32_t) const;
     uint32_t actualIndex(uint32_t) const;
     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index);
     BufferOffset actualOffset(BufferOffset) const;
     static uint32_t NopFill;
     static uint32_t GetNopFill();
@@ -1267,21 +1278,55 @@ class Assembler : public AssemblerShared
     js::Vector<BufferOffset, 0, SystemAllocPolicy> tmpPreBarriers_;
 
     CompactBufferWriter jumpRelocations_;
     CompactBufferWriter dataRelocations_;
     CompactBufferWriter preBarriers_;
 
     ARMBuffer m_buffer;
 
+#ifdef JS_DISASM_ARM
+  private:
+    class SpewNodes {
+        struct Node {
+            uint32_t key;
+            uint32_t value;
+            Node* next;
+        };
+
+        Node* nodes;
+
+    public:
+        SpewNodes() : nodes(nullptr) {}
+        ~SpewNodes();
+
+        bool lookup(uint32_t key, uint32_t* value);
+        bool add(uint32_t key, uint32_t value);
+        bool remove(uint32_t key);
+    };
+
+    SpewNodes spewNodes_;
+    uint32_t spewNext_;
+
+    bool spewDisabled();
+    uint32_t spewResolve(Label* l);
+    uint32_t spewProbe(Label* l);
+    uint32_t spewDefine(Label* l);
+    void spew(const char* fmt, ...);
+    void spew(const char* fmt, va_list args);
+#endif
+
   public:
     // For the alignment fill use NOP: 0x0320f000 or (Always | InstNOP::NopInst).
     // For the nopFill use a branch to the next instruction: 0xeaffffff.
     Assembler()
       : m_buffer(1, 1, 8, GetPoolMaxOffset(), 8, 0xe320f000, 0xeaffffff, GetNopFill()),
+#ifdef JS_DISASM_ARM
+        spewNext_(1000),
+#endif
         isFinished(false),
         dtmActive(false),
         dtmCond(Always)
     { }
 
     // We need to wait until an AutoJitContextAlloc is created by the
     // MacroAssembler, before allocating any space.
     void initWithAllocator() {
@@ -1369,17 +1414,22 @@ class Assembler : public AssemblerShared
     // Size of the data table, in bytes.
     size_t bytesNeeded() const;
 
     // Write a blob of binary into the instruction stream *OR* into a
     // destination address.
     BufferOffset writeInst(uint32_t x);
 
     // As above, but also mark the instruction as a branch.
-    BufferOffset writeBranchInst(uint32_t x);
+    BufferOffset writeBranchInst(uint32_t x, Label* documentation = nullptr);
+
+    // Write a placeholder NOP for a branch into the instruction stream
+    // (in order to adjust assembler addresses and mark it as a branch), it will
+    // be overwritten subsequently.
+    BufferOffset allocBranchInst();
 
     // A static variant for the cases where we don't want to have an assembler
     // object.
     static void WriteInstStatic(uint32_t x, uint32_t* dest);
 
   public:
     void writeCodePointer(AbsoluteLabel* label);
 
@@ -1481,17 +1531,18 @@ class Assembler : public AssemblerShared
 
     // Overwrite a pool entry with new data.
     static void WritePoolEntry(Instruction* addr, Condition c, uint32_t data);
 
     // Load a 32 bit immediate from a pool into a register.
     BufferOffset as_Imm32Pool(Register dest, uint32_t value, Condition c = Always);
     // Make a patchable jump that can target the entire 32 bit address space.
     BufferOffset as_BranchPool(uint32_t value, RepatchLabel* label,
-                               ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always);
+                               ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always,
+                               Label* documentation = nullptr);
 
     // Load a 64 bit floating point immediate from a pool into a register.
     BufferOffset as_FImm64Pool(VFPRegister dest, double value, Condition c = Always);
     // Load a 32 bit floating point immediate from a pool into a register.
     BufferOffset as_FImm32Pool(VFPRegister dest, float value, Condition c = Always);
 
     // Atomic instructions: ldrex, ldrexh, ldrexb, strex, strexh, strexb.
     //
@@ -1523,29 +1574,29 @@ class Assembler : public AssemblerShared
 
     // Control flow stuff:
 
     // bx can *only* branch to a register never to an immediate.
     BufferOffset as_bx(Register r, Condition c = Always);
 
     // Branch can branch to an immediate *or* to a register. Branches to
     // immediates are pc relative, branches to registers are absolute.
-    BufferOffset as_b(BOffImm off, Condition c);
+    BufferOffset as_b(BOffImm off, Condition c, Label* documentation = nullptr);
 
     BufferOffset as_b(Label* l, Condition c = Always);
     BufferOffset as_b(BOffImm off, Condition c, BufferOffset inst);
 
     // blx can go to either an immediate or a register. When blx'ing to a
     // register, we change processor mode depending on the low bit of the
     // register when blx'ing to an immediate, we *always* change processor
     // state.
     BufferOffset as_blx(Label* l);
 
     BufferOffset as_blx(Register r, Condition c = Always);
-    BufferOffset as_bl(BOffImm off, Condition c);
+    BufferOffset as_bl(BOffImm off, Condition c, Label* documentation = nullptr);
     // bl can only branch+link to an immediate, never to a register it never
     // changes processor state.
     BufferOffset as_bl();
     // bl #imm can have a condition code, blx #imm cannot.
     // blx reg can be conditional.
     BufferOffset as_bl(Label* l, Condition c);
     BufferOffset as_bl(BOffImm off, Condition c, BufferOffset inst);
 
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4244,20 +4244,20 @@ MacroAssemblerARMCompat::roundf(FloatReg
     // which means the result is actually -0.0 which also requires special
     // handling.
     ma_b(bail, NotSigned);
 
     bind(&fin);
 }
 
 CodeOffsetJump
-MacroAssemblerARMCompat::jumpWithPatch(RepatchLabel* label, Condition cond)
+MacroAssemblerARMCompat::jumpWithPatch(RepatchLabel* label, Condition cond, Label* documentation)
 {
     ARMBuffer::PoolEntry pe;
-    BufferOffset bo = as_BranchPool(0xdeadbeef, label, &pe, cond);
+    BufferOffset bo = as_BranchPool(0xdeadbeef, label, &pe, cond, documentation);
     // Fill in a new CodeOffset with both the load and the pool entry that the
     // instruction loads from.
     CodeOffsetJump ret(bo.getOffset(), pe.index());
     return ret;
 }
 
 void
 MacroAssemblerARMCompat::branchPtrInNurseryRange(Condition cond, Register ptr, Register temp,
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -975,19 +975,20 @@ class MacroAssemblerARMCompat : public M
         branch32(cond, lhs, imm, label);
     }
     void decBranchPtr(Condition cond, Register lhs, Imm32 imm, Label* label) {
         subPtr(imm, lhs);
         branch32(cond, lhs, Imm32(0), label);
     }
     void moveValue(const Value& val, Register type, Register data);
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always);
-    CodeOffsetJump backedgeJump(RepatchLabel* label) {
-        return jumpWithPatch(label);
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always,
+                                 Label* documentation = nullptr);
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation) {
+        return jumpWithPatch(label, Always, documentation);
     }
     template <typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, Register reg, T ptr, RepatchLabel* label) {
         ma_cmp(reg, ptr);
         return jumpWithPatch(label, cond);
     }
     template <typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, Address addr, T ptr, RepatchLabel* label) {
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -1634,17 +1634,19 @@ class MacroAssemblerCompat : public vixl
         branchTest32(cond, scratch, imm, label);
     }
     void branchTest32(Condition cond, AbsoluteAddress address, Imm32 imm, Label* label) {
         vixl::UseScratchRegisterScope temps(this);
         const Register scratch = temps.AcquireX().asUnsized();
         loadPtr(address, scratch);
         branchTest32(cond, scratch, imm, label);
     }
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always) {
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always,
+                                 Label* documentation = nullptr)
+    {
         ARMBuffer::PoolEntry pe;
         BufferOffset load_bo;
         BufferOffset branch_bo;
 
         // Does not overwrite condition codes from the caller.
         {
             vixl::UseScratchRegisterScope temps(this);
             const ARMRegister scratch64 = temps.AcquireX();
@@ -1659,18 +1661,18 @@ class MacroAssemblerCompat : public vixl
             bind(&notTaken);
         } else {
             nop();
             branch_bo = b(-1);
         }
         label->use(branch_bo.getOffset());
         return CodeOffsetJump(load_bo.getOffset(), pe.index());
     }
-    CodeOffsetJump backedgeJump(RepatchLabel* label) {
-        return jumpWithPatch(label);
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr) {
+        return jumpWithPatch(label, documentation);
     }
     template <typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, Register reg, T ptr, RepatchLabel* label) {
         cmpPtr(reg, ptr);
         return jumpWithPatch(label, cond);
     }
     template <typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, Address addr, T ptr, RepatchLabel* label) {
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -2860,17 +2860,17 @@ MacroAssemblerMIPSCompat::backedgeJump(R
     ma_liPatchable(ScratchRegister, Imm32(dest));
     as_jr(ScratchRegister);
     as_nop();
     MOZ_ASSERT(nextOffset().getOffset() - bo.getOffset() == 8 * sizeof(uint32_t));
     return CodeOffsetJump(bo.getOffset());
 }
 
 CodeOffsetJump
-MacroAssemblerMIPSCompat::jumpWithPatch(RepatchLabel* label)
+MacroAssemblerMIPSCompat::jumpWithPatch(RepatchLabel* label, Label* documentation)
 {
     // Only one branch per label.
     MOZ_ASSERT(!label->used());
     uint32_t dest = label->bound() ? label->offset() : LabelBase::INVALID_OFFSET;
 
     BufferOffset bo = nextOffset();
     label->use(bo.getOffset());
     addLongJump(bo);
--- a/js/src/jit/mips32/MacroAssembler-mips32.h
+++ b/js/src/jit/mips32/MacroAssembler-mips32.h
@@ -728,18 +728,18 @@ class MacroAssemblerMIPSCompat : public 
     }
 
 protected:
     uint32_t getType(const Value& val);
     void moveData(const Value& val, Register data);
 public:
     void moveValue(const Value& val, Register type, Register data);
 
-    CodeOffsetJump backedgeJump(RepatchLabel* label);
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label);
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr);
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Label* documentation = nullptr);
 
     template <typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, Register reg, T ptr, RepatchLabel* label) {
         movePtr(ptr, ScratchRegister);
         Label skipJump;
         ma_b(reg, ScratchRegister, &skipJump, InvertCondition(cond), ShortJump);
         CodeOffsetJump off = jumpWithPatch(label);
         bind(&skipJump);
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -228,19 +228,19 @@ class MacroAssemblerNone : public Assemb
     void tagValue(JSValueType, Register, ValueOperand) { MOZ_CRASH(); }
     void retn(Imm32 n) { MOZ_CRASH(); }
     template <typename T> void push(T) { MOZ_CRASH(); }
     template <typename T> void Push(T) { MOZ_CRASH(); }
     template <typename T> void pop(T) { MOZ_CRASH(); }
     template <typename T> void Pop(T) { MOZ_CRASH(); }
     template <typename T> CodeOffsetLabel pushWithPatch(T) { MOZ_CRASH(); }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel*) { MOZ_CRASH(); }
-    CodeOffsetJump jumpWithPatch(RepatchLabel*, Condition) { MOZ_CRASH(); }
-    CodeOffsetJump backedgeJump(RepatchLabel* label) { MOZ_CRASH(); }
+    CodeOffsetJump jumpWithPatch(RepatchLabel*, Label* doc = nullptr) { MOZ_CRASH(); }
+    CodeOffsetJump jumpWithPatch(RepatchLabel*, Condition, Label* doc = nullptr) { MOZ_CRASH(); }
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* doc = nullptr) { MOZ_CRASH(); }
     template <typename T, typename S>
     CodeOffsetJump branchPtrWithPatch(Condition, T, S, RepatchLabel*) { MOZ_CRASH(); }
 
     template <typename T, typename S> void branchTestValue(Condition, T, S, Label*) { MOZ_CRASH(); }
     void testNullSet(Condition, ValueOperand, Register) { MOZ_CRASH(); }
     void testObjectSet(Condition, ValueOperand, Register) { MOZ_CRASH(); }
     void testUndefinedSet(Condition, ValueOperand, Register) { MOZ_CRASH(); }
 
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1570,17 +1570,17 @@ CodeGeneratorShared::jumpToBlock(MBasicB
     // No jump necessary if we can fall through to the next block.
     if (isNextBlock(mir->lir()))
         return;
 
     if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
         // Note: the backedge is initially a jump to the next instruction.
         // It will be patched to the target block's label during link().
         RepatchLabel rejoin;
-        CodeOffsetJump backedge = masm.backedgeJump(&rejoin);
+        CodeOffsetJump backedge = masm.backedgeJump(&rejoin, mir->lir()->label());
         masm.bind(&rejoin);
 
         masm.propagateOOM(patchableBackedges_.append(PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
     } else {
         masm.jump(mir->lir()->label());
     }
 }
 
@@ -1591,17 +1591,17 @@ CodeGeneratorShared::jumpToBlock(MBasicB
 {
     // Skip past trivial blocks.
     mir = skipTrivialBlocks(mir);
 
     if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
         // Note: the backedge is initially a jump to the next instruction.
         // It will be patched to the target block's label during link().
         RepatchLabel rejoin;
-        CodeOffsetJump backedge = masm.jumpWithPatch(&rejoin, cond);
+        CodeOffsetJump backedge = masm.jumpWithPatch(&rejoin, cond, mir->lir()->label());
         masm.bind(&rejoin);
 
         masm.propagateOOM(patchableBackedges_.append(PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
     } else {
         masm.j(cond, mir->lir()->label());
     }
 }
 #endif
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -644,27 +644,29 @@ class MacroAssemblerX64 : public MacroAs
     }
 
     template <typename T, typename S>
     void branchPtr(Condition cond, T lhs, S ptr, Label* label) {
         cmpPtr(Operand(lhs), ptr);
         j(cond, label);
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label) {
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Label* documentation = nullptr) {
         JmpSrc src = jmpSrc(label);
         return CodeOffsetJump(size(), addPatchableJump(src, Relocation::HARDCODED));
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond) {
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond,
+                                 Label* documentation = nullptr)
+    {
         JmpSrc src = jSrc(cond, label);
         return CodeOffsetJump(size(), addPatchableJump(src, Relocation::HARDCODED));
     }
 
-    CodeOffsetJump backedgeJump(RepatchLabel* label) {
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr) {
         return jumpWithPatch(label);
     }
 
     template <typename S, typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, S lhs, T ptr, RepatchLabel* label) {
         cmpPtr(lhs, ptr);
         return jumpWithPatch(label, cond);
     }
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -662,27 +662,29 @@ class MacroAssemblerX86 : public MacroAs
     }
 
     template <typename T, typename S>
     void branchPtr(Condition cond, T lhs, S ptr, RepatchLabel* label) {
         cmpPtr(Operand(lhs), ptr);
         j(cond, label);
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label) {
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Label* documentation = nullptr) {
         jump(label);
         return CodeOffsetJump(size());
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Assembler::Condition cond) {
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Assembler::Condition cond,
+                                 Label* documentation = nullptr)
+    {
         j(cond, label);
         return CodeOffsetJump(size());
     }
 
-    CodeOffsetJump backedgeJump(RepatchLabel* label) {
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr) {
         return jumpWithPatch(label);
     }
 
     template <typename S, typename T>
     CodeOffsetJump branchPtrWithPatch(Condition cond, S lhs, T ptr, RepatchLabel* label) {
         branchPtr(cond, lhs, ptr, label);
         return CodeOffsetJump(size());
     }