Bug 1430161 - Factor ARM disassembler, implement for ARM64. r=nbp
authorLars T Hansen <lhansen@mozilla.com>
Fri, 15 Dec 2017 13:10:23 -0600
changeset 455775 045ded11d3f810ea430b0eed3026534f2508d955
parent 455774 5dee3b871181c700fd507f95c18be5a650b6da56
child 455776 4f58e75a8a20205da6828ce9556f0b31f0b5a4ba
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1430161
milestone60.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 1430161 - Factor ARM disassembler, implement for ARM64. r=nbp We extract the ARM disassembler bits and place them in jit/shared/Disassembler-shared.{cpp,h}, and then clean them up and generalize them. The ARM assembler is slightly modified to deal with this but the changes are local. We then add code to the ARM64 assembler to drive the disassembler. The structure is as for the ARM disassembler.
js/moz.configure
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/Simulator-arm.cpp
js/src/jit/arm64/Assembler-arm64.cpp
js/src/jit/arm64/Assembler-arm64.h
js/src/jit/arm64/MacroAssembler-arm64.cpp
js/src/jit/arm64/MacroAssembler-arm64.h
js/src/jit/arm64/vixl/Assembler-vixl.cpp
js/src/jit/arm64/vixl/Assembler-vixl.h
js/src/jit/arm64/vixl/Disasm-vixl.cpp
js/src/jit/arm64/vixl/Disasm-vixl.h
js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
js/src/jit/shared/Disassembler-shared.cpp
js/src/jit/shared/Disassembler-shared.h
js/src/moz.build
js/src/wasm/WasmStubs.cpp
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -179,26 +179,41 @@ set_define('JS_CODEGEN_MIPS64', jit_code
 set_define('JS_CODEGEN_X86', jit_codegen.x86)
 set_define('JS_CODEGEN_X64', jit_codegen.x64)
 
 @depends('--enable-ion', simulator, target, moz_debug)
 def jit_disasm_arm(ion_enabled, simulator, target, debug):
     if not ion_enabled:
         return
 
-    if simulator:
+    if simulator and debug:
         if getattr(simulator, 'arm', None):
             return True
 
     if target.cpu == 'arm' and debug:
         return True
 
 set_config('JS_DISASM_ARM', jit_disasm_arm)
 set_define('JS_DISASM_ARM', jit_disasm_arm)
 
+@depends('--enable-ion', simulator, target, moz_debug)
+def jit_disasm_arm64(ion_enabled, simulator, target, debug):
+    if not ion_enabled:
+        return
+
+    if simulator and debug:
+        if getattr(simulator, 'arm64', None):
+            return True
+
+    if target.cpu == 'aarch64' and debug:
+        return True
+
+set_config('JS_DISASM_ARM64', jit_disasm_arm64)
+set_define('JS_DISASM_ARM64', jit_disasm_arm64)
+
 # Profiling
 # =======================================================
 js_option('--enable-instruments', env='MOZ_INSTRUMENTS',
           help='Enable instruments remote profiling')
 
 @depends('--enable-instruments', target)
 def instruments(value, target):
     if value and target.os != 'OSX':
--- a/js/src/jit/arm/Assembler-arm.cpp
+++ b/js/src/jit/arm/Assembler-arm.cpp
@@ -5,33 +5,33 @@
  * 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"
 #include "jit/MacroAssembler.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::CountLeadingZeroes32;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
 void dbg_break() {}
 
 // The ABIArgGenerator is used for making system ABI calls and for inter-wasm
 // calls. The system ABI can either be SoftFp or HardFp, and inter-wasm calls
 // are always HardFp calls. The initialization defaults to HardFp, and the ABI
 // choice is made before any system ABI calls with the method "setUseHardFp".
 ABIArgGenerator::ABIArgGenerator()
   : intRegIndex_(0),
@@ -1435,251 +1435,16 @@ Assembler::dataRelocationTableBytes() co
 size_t
 Assembler::bytesNeeded() const
 {
     return size() +
         jumpRelocationTableBytes() +
         dataRelocationTableBytes();
 }
 
-#ifdef JS_DISASM_ARM
-
-void
-Assembler::spewInst(Instruction* i)
-{
-    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);
-    printf("   %08x  %s\n", reinterpret_cast<uint32_t>(loc), buffer.start());
-}
-
-// 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() || !i)
-        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() || !i)
-        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)
-        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) {
-                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;
-    Instruction* inst = m_buffer.getInstOrNull(addr);
-    if (!inst)
-        return;
-    uint32_t *instr = reinterpret_cast<uint32_t*>(inst);
-    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) || printer_);
-}
-
-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)
-{
-    if (printer_) {
-        printer_->vprintf(fmt, va);
-        printer_->put("\n");
-    }
-    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
-
 // 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);
 }
@@ -2131,95 +1896,109 @@ 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);
 }
 
 BufferOffset
-Assembler::allocEntry(size_t numInst, unsigned numPoolEntries,
-                      uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe,
-                      bool loadToPC)
+Assembler::allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+                                 PoolHintPun& php, uint8_t* data,
+                                 const LiteralDoc& doc,
+                                 ARMBuffer::PoolEntry* pe, bool loadToPC)
 {
+    uint8_t* inst = (uint8_t*)&php.raw;
+
+    MOZ_ASSERT(inst);
+    MOZ_ASSERT(numInst == 1);   // Or fix the disassembly
+
     BufferOffset offs = m_buffer.allocEntry(numInst, numPoolEntries, inst, data, pe);
     propagateOOM(offs.assigned());
 #ifdef JS_DISASM_ARM
-    spewData(offs, numInst, loadToPC);
+    Instruction* instruction = m_buffer.getInstOrNull(offs);
+    if (instruction)
+        spewLiteralLoad(php, loadToPC, instruction, doc);
 #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);
-    BufferOffset offs = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, nullptr, dest == pc);
+    BufferOffset offs = allocLiteralLoadEntry(1, 1, php,
+                                              (uint8_t*)&value,
+                                              LiteralDoc(value),
+                                              nullptr,
+                                              dest == pc);
     return offs;
 }
 
 /* static */ void
 Assembler::WritePoolEntry(Instruction* addr, Condition c, uint32_t data)
 {
     MOZ_ASSERT(addr->is<InstLDR>());
     *addr->as<InstLDR>()->dest() = data;
     MOZ_ASSERT(addr->extractCond() == c);
 }
 
 BufferOffset
-Assembler::as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c,
-                         Label* documentation)
+Assembler::as_BranchPool(uint32_t value, RepatchLabel* label,
+                         const LabelDoc& documentation,
+                         ARMBuffer::PoolEntry* pe, Condition c)
 {
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolBranch, pc);
-    BufferOffset ret = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe,
-                                  /* loadToPC = */ true);
+    BufferOffset ret = allocLiteralLoadEntry(1, 1, php, (uint8_t*)&value,
+                                             LiteralDoc(),
+                                             pe,
+                                             /* loadToPC = */ true);
     // If this label is already bound, then immediately replace the stub load
     // with a correct branch.
     if (label->bound()) {
         BufferOffset dest(label);
         BOffImm offset = dest.diffB<BOffImm>(ret);
         if (offset.isInvalid()) {
             m_buffer.fail_bail();
             return ret;
         }
         as_b(offset, c, ret);
     } else if (!oom()) {
         label->use(ret.getOffset());
     }
 #ifdef JS_DISASM_ARM
-    if (documentation)
-        spewTarget(documentation);
+    spew_.spewRef(documentation);
 #endif
     return ret;
 }
 
 BufferOffset
 Assembler::as_FImm64Pool(VFPRegister dest, double d, Condition c)
 {
     MOZ_ASSERT(dest.isDouble());
     PoolHintPun php;
     php.phd.init(0, c, PoolHintData::PoolVDTR, dest);
-    return allocEntry(1, 2, (uint8_t*)&php.raw, (uint8_t*)&d);
+    return allocLiteralLoadEntry(1, 2, php, (uint8_t*)&d, LiteralDoc(d));
 }
 
 BufferOffset
 Assembler::as_FImm32Pool(VFPRegister dest, float f, 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 allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&f);
+    return allocLiteralLoadEntry(1, 1, php, (uint8_t*)&f, LiteralDoc(f));
 }
 
 // Pool callbacks stuff:
 void
 Assembler::InsertIndexIntoTag(uint8_t* load_, uint32_t index)
 {
     uint32_t* load = (uint32_t*)load_;
     PoolHintPun php;
@@ -2402,32 +2181,31 @@ Assembler::WritePoolGuard(BufferOffset b
     *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, Label* documentation)
 {
-    BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode(), documentation);
-    return ret;
+    return writeBranchInst(((int)c) | OpB | off.encode(), refLabel(documentation));
 }
 
 BufferOffset
 Assembler::as_b(Label* l, Condition c)
 {
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
         if (oom())
             return BufferOffset();
 
         as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
 #ifdef JS_DISASM_ARM
-        spewBranch(m_buffer.getInstOrNull(ret), l);
+        spewBranch(m_buffer.getInstOrNull(ret), refLabel(l));
 #endif
         return ret;
     }
 
     if (oom())
         return BufferOffset();
 
     BufferOffset ret;
@@ -2481,17 +2259,17 @@ Assembler::as_blx(Register r, Condition 
     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, Label* documentation)
 {
-    return writeBranchInst(((int)c) | OpBl | off.encode(), documentation);
+    return writeBranchInst(((int)c) | OpBl | off.encode(), refLabel(documentation));
 }
 
 BufferOffset
 Assembler::as_bl(Label* l, Condition c)
 {
     if (l->bound()) {
         // Note only one instruction is emitted here, the NOP is overwritten.
         BufferOffset ret = allocBranchInst();
@@ -2501,17 +2279,17 @@ Assembler::as_bl(Label* l, Condition c)
         BOffImm offset = BufferOffset(l).diffB<BOffImm>(ret);
         if (offset.isInvalid()) {
             m_buffer.fail_bail();
             return BufferOffset();
         }
 
         as_bl(offset, c, ret);
 #ifdef JS_DISASM_ARM
-        spewBranch(m_buffer.getInstOrNull(ret), l);
+        spewBranch(m_buffer.getInstOrNull(ret), refLabel(l));
 #endif
         return ret;
     }
 
     if (oom())
         return BufferOffset();
 
     BufferOffset ret;
@@ -2850,17 +2628,17 @@ Assembler::nextLink(BufferOffset b, Buff
     new (next) BufferOffset(destOff.decode());
     return true;
 }
 
 void
 Assembler::bind(Label* label, BufferOffset boff)
 {
 #ifdef JS_DISASM_ARM
-    spewLabel(label);
+    spew_.spewBind(label);
 #endif
     if (oom()) {
         // Ensure we always bind the label. This matches what we do on
         // x86/x64 and silences the assert in ~Label.
         label->bind(0);
         return;
     }
 
@@ -2936,17 +2714,17 @@ Assembler::bind(RepatchLabel* label)
     }
     label->bind(dest.getOffset());
 }
 
 void
 Assembler::retarget(Label* label, Label* target)
 {
 #ifdef JS_DISASM_ARM
-    spewRetarget(label, target);
+    spew_.spewRetarget(label, target);
 #endif
     if (label->used() && !oom()) {
         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);
@@ -3486,8 +3264,171 @@ Assembler::GetPoolMaxOffset()
     }
     return AsmPoolMaxOffset;
 }
 
 SecondScratchRegisterScope::SecondScratchRegisterScope(MacroAssembler &masm)
   : AutoRegisterScope(masm, masm.getSecondScratchReg())
 {
 }
+
+#ifdef JS_DISASM_ARM
+
+/* static */ void
+Assembler::disassembleInstruction(const Instruction* i, DisasmBuffer& buffer)
+{
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw()));
+    dasm.InstructionDecode(buffer, loc);
+}
+
+void
+Assembler::initDisassembler()
+{
+    // The line is normally laid out like this:
+    //
+    // xxxxxxxx        ldr r, op   ; comment
+    //
+    // where xx...x is the instruction bit pattern.
+    //
+    // Labels are laid out by themselves to line up with the instructions above
+    // and below:
+    //
+    //            nnnn:
+    //
+    // Branch targets are normally on the same line as the branch instruction,
+    // but when they cannot be they will be on a line by themselves, indented
+    // significantly:
+    //
+    //                     -> label
+
+    spew_.setLabelIndent("          ");             // 10
+    spew_.setTargetIndent("                    ");  // 20
+}
+
+void
+Assembler::finishDisassembler()
+{
+    spew_.spewOrphans();
+}
+
+// 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 spew_.refLabel().
+//
+// 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 allocLiteralLoadEntry() 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 (spew_.isDisabled() || !i)
+        return;
+
+    DisasmBuffer buffer;
+    disassembleInstruction(i, buffer);
+    spew_.spew("%s", buffer.start());
+}
+
+// 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, const LabelDoc& target)
+{
+    if (spew_.isDisabled() || !i)
+        return;
+
+    DisasmBuffer buffer;
+    disassembleInstruction(i, buffer);
+
+    char labelBuf[128];
+    labelBuf[0] = 0;
+
+    bool haveTarget = target.valid;
+    if (!haveTarget)
+        snprintf(labelBuf, sizeof(labelBuf), "  -> (link-time target)");
+
+    if (InstBranchImm::IsTHIS(*i)) {
+        InstBranchImm* bimm = InstBranchImm::AsTHIS(*i);
+        BOffImm destOff;
+        bimm->extractImm(&destOff);
+        if (destOff.isInvalid() || haveTarget) {
+            // 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 (haveTarget) {
+                snprintf(labelBuf, sizeof(labelBuf), "  -> %d%s", target.doc,
+                         !target.bound ? "f" : "");
+                haveTarget = false;
+            }
+        }
+    }
+    spew_.spew("%s%s", buffer.start(), labelBuf);
+
+    if (haveTarget)
+        spew_.spewRef(target);
+}
+
+void
+Assembler::spewLiteralLoad(PoolHintPun& php, bool loadToPC, const Instruction* i,
+                           const LiteralDoc& doc)
+{
+    if (spew_.isDisabled())
+        return;
+
+    char litbuf[2048];
+    spew_.formatLiteral(doc, litbuf, sizeof(litbuf));
+
+    // See patchConstantPoolLoad, above.  We assemble the instruction into a
+    // buffer with a zero offset, as documentation, but the offset will be
+    // patched later.
+
+    uint32_t inst;
+    PoolHintData& data = php.phd;
+    switch (php.phd.getLoadType()) {
+      case PoolHintData::PoolDTR:
+        Assembler::as_dtr_patch(IsLoad, 32, Offset, data.getReg(),
+                                DTRAddr(pc, DtrOffImm(0)),
+                                data.getCond(), &inst);
+        break;
+      case PoolHintData::PoolBranch:
+        if (data.isValidPoolHint()) {
+            Assembler::as_dtr_patch(IsLoad, 32, Offset, pc,
+                                    DTRAddr(pc, DtrOffImm(0)),
+                                    data.getCond(), &inst);
+        }
+        break;
+      case PoolHintData::PoolVDTR:
+        Assembler::as_vdtr_patch(IsLoad, data.getVFPReg(), VFPAddr(pc, VFPOffImm(0)),
+                                 data.getCond(), &inst);
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+
+    DisasmBuffer buffer;
+    disasm::NameConverter converter;
+    disasm::Disassembler dasm(converter);
+    dasm.InstructionDecode(buffer, reinterpret_cast<uint8_t*>(&inst));
+    spew_.spew("%s    ; .const %s", buffer.start(), litbuf);
+}
+
+#endif // JS_DISASM_ARM
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -7,25 +7,32 @@
 #ifndef jit_arm_Assembler_arm_h
 #define jit_arm_Assembler_arm_h
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include "jit/arm/Architecture-arm.h"
+#include "jit/arm/disasm/Disasm-arm.h"
 #include "jit/CompactBuffer.h"
 #include "jit/IonCode.h"
 #include "jit/JitCompartment.h"
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
+union PoolHintPun;
+
 namespace js {
 namespace jit {
 
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+using LabelDoc = DisassemblerSpew::LabelDoc;
+
 // NOTE: there are duplicates in this list! Sometimes we want to specifically
 // refer to the link register as a link register (bl lr is much clearer than bl
 // r14). HOWEVER, this register can easily be a gpr when it is not busy holding
 // the return address.
 static constexpr Register r0  { Registers::r0 };
 static constexpr Register r1  { Registers::r1 };
 static constexpr Register r2  { Registers::r2 };
 static constexpr Register r3  { Registers::r3 };
@@ -1238,32 +1245,37 @@ class Assembler : public AssemblerShared
     // needs to go out here :(
 
     BufferOffset nextOffset() {
         return m_buffer.nextOffset();
     }
 
   protected:
     // Shim around AssemblerBufferWithConstantPools::allocEntry.
-    BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
-                            uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe = nullptr,
-                            bool loadToPC = false);
+    BufferOffset allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+                                       PoolHintPun& php, uint8_t* data,
+                                       const LiteralDoc& doc = LiteralDoc(),
+                                       ARMBuffer::PoolEntry* pe = nullptr,
+                                       bool loadToPC = false);
 
     Instruction* editSrc(BufferOffset bo) {
         return m_buffer.getInst(bo);
     }
 
 #ifdef JS_DISASM_ARM
-    static void spewInst(Instruction* i);
+    typedef disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> DisasmBuffer;
+
+    static void disassembleInstruction(const Instruction* i, DisasmBuffer& buffer);
+
+    void initDisassembler();
+    void finishDisassembler();
     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);
+    void spewBranch(Instruction* i, const LabelDoc& target);
+    void spewLiteralLoad(PoolHintPun& php, bool loadToPC, const Instruction* offs,
+                         const LiteralDoc& doc);
 #endif
 
   public:
     void resetCounter();
     uint32_t actualIndex(uint32_t) const;
     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index);
     static uint32_t NopFill;
     static uint32_t GetNopFill();
@@ -1291,60 +1303,38 @@ class Assembler : public AssemblerShared
     js::Vector<RelativePatch, 8, SystemAllocPolicy> jumps_;
 
     CompactBufferWriter jumpRelocations_;
     CompactBufferWriter dataRelocations_;
 
     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_;
-    Sprinter* printer_;
-
-    bool spewDisabled();
-    uint32_t spewResolve(Label* l);
-    uint32_t spewProbe(Label* l);
-    uint32_t spewDefine(Label* l);
-    void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
-    void spew(const char* fmt, va_list args) MOZ_FORMAT_PRINTF(2, 0);
+    DisassemblerSpew spew_;
 #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),
-        printer_(nullptr),
-#endif
         isFinished(false),
         dtmActive(false),
         dtmCond(Always)
-    { }
+    {
+#ifdef JS_DISASM_ARM
+        initDisassembler();
+#endif
+    }
+
+    ~Assembler() {
+#ifdef JS_DISASM_ARM
+        finishDisassembler();
+#endif
+    }
 
     // We need to wait until an AutoJitContextAlloc is created by the
     // MacroAssembler, before allocating any space.
     void initWithAllocator() {
         m_buffer.initWithAllocator();
     }
 
     static Condition InvertCondition(Condition cond);
@@ -1392,26 +1382,36 @@ class Assembler : public AssemblerShared
     static uintptr_t GetPointer(uint8_t*);
     template <class Iter>
     static const uint32_t* GetPtr32Target(Iter iter, Register* dest = nullptr, RelocStyle* rs = nullptr);
 
     bool oom() const;
 
     void setPrinter(Sprinter* sp) {
 #ifdef JS_DISASM_ARM
-        printer_ = sp;
+        spew_.setPrinter(sp);
 #endif
     }
 
     static const Register getStackPointer() {
         return StackPointer;
     }
 
   private:
     bool isFinished;
+
+  protected:
+    LabelDoc refLabel(const Label* label) {
+#ifdef JS_DISASM_ARM
+        return spew_.refLabel(label);
+#else
+        return LabelDoc();
+#endif
+    }
+
   public:
     void finish();
     bool appendRawCode(const uint8_t* code, size_t numBytes);
     bool reserve(size_t size);
     bool swapBuffer(wasm::Bytes& bytes);
     void copyJumpRelocationTable(uint8_t* dest);
     void copyDataRelocationTable(uint8_t* dest);
 
@@ -1431,17 +1431,18 @@ class Assembler : public AssemblerShared
 #ifdef JS_DISASM_ARM
         spew(m_buffer.getInstOrNull(offs));
 #endif
         return offs;
     }
 
     // As above, but also mark the instruction as a branch.  Very hot, inlined
     // for performance
-    MOZ_ALWAYS_INLINE BufferOffset writeBranchInst(uint32_t x, Label* documentation = nullptr) {
+    MOZ_ALWAYS_INLINE BufferOffset
+    writeBranchInst(uint32_t x, const LabelDoc& documentation) {
         BufferOffset offs = m_buffer.putInt(x);
 #ifdef JS_DISASM_ARM
         spewBranch(m_buffer.getInstOrNull(offs), documentation);
 #endif
         return offs;
     }
 
     // Write a placeholder NOP for a branch into the instruction stream
@@ -1554,18 +1555,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,
-                               Label* documentation = nullptr);
+                               const LabelDoc& documentation,
+                               ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always);
 
     // 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: ldrexd, ldrex, ldrexh, ldrexb, strexd, strex, strexh, strexb.
     //
@@ -1764,17 +1765,17 @@ class Assembler : public AssemblerShared
     void flush() {
         MOZ_ASSERT(!isFinished);
         m_buffer.flushPool();
         return;
     }
 
     void comment(const char* msg) {
 #ifdef JS_DISASM_ARM
-        spew("; %s", msg);
+        spew_.spew("; %s", msg);
 #endif
     }
 
     // Copy the assembly code to the given buffer, and perform any pending
     // relocations relying on the target address.
     void executableCopy(uint8_t* buffer, bool flushICache = true);
 
     // Actual assembly emitting functions.
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4147,17 +4147,17 @@ MacroAssemblerARMCompat::roundf(FloatReg
 
     bind(&fin);
 }
 
 CodeOffsetJump
 MacroAssemblerARMCompat::jumpWithPatch(RepatchLabel* label, Condition cond, Label* documentation)
 {
     ARMBuffer::PoolEntry pe;
-    BufferOffset bo = as_BranchPool(0xdeadbeef, label, &pe, cond, documentation);
+    BufferOffset bo = as_BranchPool(0xdeadbeef, label, refLabel(documentation), &pe, cond);
     // 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::profilerEnterFrame(Register framePtr, Register scratch)
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -60,16 +60,43 @@ int64_t
     uint32_t hi = uint32_t(x) % uint32_t(y);
     return (int64_t(hi) << 32) | lo;
 }
 }
 
 namespace js {
 namespace jit {
 
+// For decoding load-exclusive and store-exclusive instructions.
+namespace excl {
+
+// Bit positions.
+enum {
+    ExclusiveOpHi = 24,         // Hi bit of opcode field
+    ExclusiveOpLo = 23,         // Lo bit of opcode field
+    ExclusiveSizeHi = 22,       // Hi bit of operand size field
+    ExclusiveSizeLo = 21,       // Lo bit of operand size field
+    ExclusiveLoad = 20          // Bit indicating load
+};
+
+// Opcode bits for exclusive instructions.
+enum {
+    ExclusiveOpcode = 3
+};
+
+// Operand size, Bits(ExclusiveSizeHi,ExclusiveSizeLo).
+enum {
+    ExclusiveWord = 0,
+    ExclusiveDouble = 1,
+    ExclusiveByte = 2,
+    ExclusiveHalf = 3
+};
+
+}
+
 // Load/store multiple addressing mode.
 enum BlockAddrMode {
     // Alias modes for comparison when writeback does not matter.
     da_x         = (0|0|0) << 21,  // Decrement after.
     ia_x         = (0|4|0) << 21,  // Increment after.
     db_x         = (8|0|0) << 21,  // Decrement before.
     ib_x         = (8|4|0) << 21,  // Increment before.
 };
@@ -408,25 +435,27 @@ void
 Simulator::Destroy(Simulator* sim)
 {
     js_delete(sim);
 }
 
 void
 Simulator::disassemble(SimInstruction* instr, size_t n)
 {
+#ifdef JS_DISASM_ARM
     disasm::NameConverter converter;
     disasm::Disassembler dasm(converter);
     disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer;
     while (n-- > 0) {
         dasm.InstructionDecode(buffer,
                                reinterpret_cast<uint8_t*>(instr));
         fprintf(stderr, "  0x%08x  %s\n", uint32_t(instr), buffer.start());
         instr = reinterpret_cast<SimInstruction*>(reinterpret_cast<uint8_t*>(instr) + 4);
     }
+#endif
 }
 
 void
 Simulator::disasm(SimInstruction* instr)
 {
     disassemble(instr, 1);
 }
 
@@ -3099,62 +3128,62 @@ Simulator::decodeType01(SimInstruction* 
                         lo_res = static_cast<int32_t>(result & 0xffffffff);
                     }
                     set_register(rd_lo, lo_res);
                     set_register(rd_hi, hi_res);
                     if (instr->hasS())
                         MOZ_CRASH();
                 }
             } else {
-                if (instr->bits(disasm::ExclusiveOpHi, disasm::ExclusiveOpLo) == disasm::ExclusiveOpcode) {
+                if (instr->bits(excl::ExclusiveOpHi, excl::ExclusiveOpLo) == excl::ExclusiveOpcode) {
                     // Load-exclusive / store-exclusive.
-                    if (instr->bit(disasm::ExclusiveLoad)) {
+                    if (instr->bit(excl::ExclusiveLoad)) {
                         int rn = instr->rnValue();
                         int rt = instr->rtValue();
                         int32_t address = get_register(rn);
-                        switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) {
-                          case disasm::ExclusiveWord:
+                        switch (instr->bits(excl::ExclusiveSizeHi, excl::ExclusiveSizeLo)) {
+                          case excl::ExclusiveWord:
                             set_register(rt, readExW(address, instr));
                             break;
-                          case disasm::ExclusiveDouble: {
+                          case excl::ExclusiveDouble: {
                             MOZ_ASSERT((rt % 2) == 0);
                             int32_t hibits;
                             int32_t lobits = readExDW(address, &hibits);
                             set_register(rt, lobits);
                             set_register(rt+1, hibits);
                             break;
                           }
-                          case disasm::ExclusiveByte:
+                          case excl::ExclusiveByte:
                             set_register(rt, readExBU(address));
                             break;
-                          case disasm::ExclusiveHalf:
+                          case excl::ExclusiveHalf:
                             set_register(rt, readExHU(address, instr));
                             break;
                         }
                     } else {
                         int rn = instr->rnValue();
                         int rd = instr->rdValue();
                         int rt = instr->bits(3,0);
                         int32_t address = get_register(rn);
                         int32_t value = get_register(rt);
                         int32_t result = 0;
-                        switch (instr->bits(disasm::ExclusiveSizeHi, disasm::ExclusiveSizeLo)) {
-                          case disasm::ExclusiveWord:
+                        switch (instr->bits(excl::ExclusiveSizeHi, excl::ExclusiveSizeLo)) {
+                          case excl::ExclusiveWord:
                             result = writeExW(address, value, instr);
                             break;
-                          case disasm::ExclusiveDouble: {
+                          case excl::ExclusiveDouble: {
                             MOZ_ASSERT((rt % 2) == 0);
                             int32_t value2 = get_register(rt+1);
                             result = writeExDW(address, value, value2);
                             break;
                           }
-                          case disasm::ExclusiveByte:
+                          case excl::ExclusiveByte:
                             result = writeExB(address, (uint8_t)value);
                             break;
-                          case disasm::ExclusiveHalf:
+                          case excl::ExclusiveHalf:
                             result = writeExH(address, (uint16_t)value, instr);
                             break;
                         }
                         set_register(rd, result);
                     }
                 } else {
                     MOZ_CRASH(); // Not used atm
                 }
--- a/js/src/jit/arm64/Assembler-arm64.cpp
+++ b/js/src/jit/arm64/Assembler-arm64.cpp
@@ -11,16 +11,17 @@
 
 #include "jscompartment.h"
 #include "jsutil.h"
 
 #include "gc/Marking.h"
 
 #include "jit/arm64/Architecture-arm64.h"
 #include "jit/arm64/MacroAssembler-arm64.h"
+#include "jit/arm64/vixl/Disasm-vixl.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/JitCompartment.h"
 
 #include "gc/StoreBuffer-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
@@ -162,61 +163,67 @@ Assembler::executableCopy(uint8_t* buffe
         }
     }
 
     if (flushICache)
         AutoFlushICache::setRange(uintptr_t(buffer), armbuffer_.size());
 }
 
 BufferOffset
-Assembler::immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op, ARMBuffer::PoolEntry* pe)
+Assembler::immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                   const LiteralDoc& doc, ARMBuffer::PoolEntry* pe)
 {
     uint32_t inst = op | Rt(dest);
     const size_t numInst = 1;
     const unsigned sizeOfPoolEntryInBytes = 4;
     const unsigned numPoolEntries = sizeof(value) / sizeOfPoolEntryInBytes;
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, pe);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, doc, pe);
 }
 
 BufferOffset
 Assembler::immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe)
 {
-    return immPool(dest, (uint8_t*)&value, vixl::LDR_x_lit, pe);
+    return immPool(dest, (uint8_t*)&value, vixl::LDR_x_lit, LiteralDoc(value), pe);
 }
 
 BufferOffset
 Assembler::immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c)
 {
     MOZ_CRASH("immPool64Branch");
 }
 
 BufferOffset
-Assembler::fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op)
+Assembler::fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                    const LiteralDoc& doc)
 {
     uint32_t inst = op | Rt(dest);
     const size_t numInst = 1;
     const unsigned sizeOfPoolEntryInBits = 32;
     const unsigned numPoolEntries = dest.size() / sizeOfPoolEntryInBits;
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, doc);
 }
 
 BufferOffset
 Assembler::fImmPool64(ARMFPRegister dest, double value)
 {
-    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_d_lit);
+    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_d_lit, LiteralDoc(value));
 }
+
 BufferOffset
 Assembler::fImmPool32(ARMFPRegister dest, float value)
 {
-    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit);
+    return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit, LiteralDoc(value));
 }
 
 void
 Assembler::bind(Label* label, BufferOffset targetOffset)
 {
+#ifdef JS_DISASM_ARM64
+    spew_.spewBind(label);
+#endif
     // Nothing has seen the label yet: just mark the location.
     // If we've run out of memory, don't attempt to modify the buffer which may
     // not be there. Just mark the label as bound to the (possibly bogus)
     // targetOffset.
     if (!label->used() || oom()) {
         label->bind(targetOffset.getOffset());
         return;
     }
@@ -629,16 +636,19 @@ Assembler::FixupNurseryObjects(JSContext
 
     if (hasNurseryPointers)
         cx->zone()->group()->storeBuffer().putWholeCell(code);
 }
 
 void
 Assembler::retarget(Label* label, Label* target)
 {
+#ifdef JS_DISASM_ARM64
+    spew_.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);
 
--- a/js/src/jit/arm64/Assembler-arm64.h
+++ b/js/src/jit/arm64/Assembler-arm64.h
@@ -5,26 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef A64_ASSEMBLER_A64_H_
 #define A64_ASSEMBLER_A64_H_
 
 #include "jit/arm64/vixl/Assembler-vixl.h"
 
 #include "jit/JitCompartment.h"
+#include "jit/shared/Disassembler-shared.h"
 
 namespace js {
 namespace jit {
 
 // VIXL imports.
 typedef vixl::Register ARMRegister;
 typedef vixl::FPRegister ARMFPRegister;
 using vixl::ARMBuffer;
 using vixl::Instruction;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
 static const uint32_t AlignmentAtPrologue = 0;
 static const uint32_t AlignmentMidPrologue = 8;
 static const Scale ScalePointer = TimesEight;
 
 // The MacroAssembler uses scratch registers extensively and unexpectedly.
 // For safety, scratch registers should always be acquired using
 // vixl::UseScratchRegisterScope.
 static constexpr Register ScratchReg { Registers::ip0 };
@@ -200,20 +204,21 @@ class Assembler : public vixl::Assembler
     void trace(JSTracer* trc);
 
     // Emit the jump table, returning the BufferOffset to the first entry in the table.
     BufferOffset emitExtendedJumpTable();
     BufferOffset ExtendedJumpTable_;
     void executableCopy(uint8_t* buffer, bool flushICache = true);
 
     BufferOffset immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
-                         ARMBuffer::PoolEntry* pe = nullptr);
+                         const LiteralDoc& doc, ARMBuffer::PoolEntry* pe = nullptr);
     BufferOffset immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe = nullptr);
     BufferOffset immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, vixl::Condition c);
-    BufferOffset fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op);
+    BufferOffset fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op,
+                          const LiteralDoc& doc);
     BufferOffset fImmPool64(ARMFPRegister dest, double value);
     BufferOffset fImmPool32(ARMFPRegister dest, float value);
 
     void bind(Label* label) { bind(label, nextOffset()); }
     void bind(Label* label, BufferOffset boff);
     void bind(RepatchLabel* label);
     void bindLater(Label* label, wasm::OldTrapDesc target) {
         MOZ_CRASH("NYI");
@@ -262,28 +267,33 @@ class Assembler : public vixl::Assembler
 
     // The buffer is about to be linked. Ensure any constant pools or
     // excess bookkeeping has been flushed to the instruction stream.
     void flush() {
         armbuffer_.flushPool();
     }
 
     void comment(const char* msg) {
-        // This is not implemented because setPrinter() is not implemented.
-        // TODO spew("; %s", msg);
+#ifdef JS_DISASM_ARM64
+        spew_.spew("; %s", msg);
+#endif
     }
 
     int actualIndex(int curOffset) {
         ARMBuffer::PoolEntry pe(curOffset);
         return armbuffer_.poolEntryOffset(pe);
     }
     static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index) {
         return code->raw() + index;
     }
+
     void setPrinter(Sprinter* sp) {
+#ifdef JS_DISASM_ARM64
+        spew_.setPrinter(sp);
+#endif
     }
 
     static bool SupportsFloatingPoint() { return true; }
     static bool SupportsUnalignedAccesses() { return true; }
     static bool SupportsSimd() { return js::jit::SupportsSimd; }
 
     static bool HasRoundInstruction(RoundingMode mode) { return false; }
 
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -76,57 +76,57 @@ BufferOffset
 MacroAssemblerCompat::movePatchablePtr(ImmPtr ptr, Register dest)
 {
     const size_t numInst = 1; // Inserting one load instruction.
     const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
     uint8_t* literalAddr = (uint8_t*)(&ptr.value); // TODO: Should be const.
 
     // Scratch space for generating the load instruction.
     //
-    // allocEntry() will use InsertIndexIntoTag() to store a temporary
+    // allocLiteralLoadEntry() will use InsertIndexIntoTag() to store a temporary
     // index to the corresponding PoolEntry in the instruction itself.
     //
     // That index will be fixed up later when finishPool()
     // walks over all marked loads and calls PatchConstantPoolLoad().
     uint32_t instructionScratch = 0;
 
     // Emit the instruction mask in the scratch space.
     // The offset doesn't matter: it will be fixed up later.
     vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
 
     // Add the entry to the pool, fix up the LDR imm19 offset,
     // and add the completed instruction to the buffer.
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
-                      literalAddr);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
+                                 literalAddr);
 }
 
 BufferOffset
 MacroAssemblerCompat::movePatchablePtr(ImmWord ptr, Register dest)
 {
     const size_t numInst = 1; // Inserting one load instruction.
     const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
     uint8_t* literalAddr = (uint8_t*)(&ptr.value);
 
     // Scratch space for generating the load instruction.
     //
-    // allocEntry() will use InsertIndexIntoTag() to store a temporary
+    // allocLiteralLoadEntry() will use InsertIndexIntoTag() to store a temporary
     // index to the corresponding PoolEntry in the instruction itself.
     //
     // That index will be fixed up later when finishPool()
     // walks over all marked loads and calls PatchConstantPoolLoad().
     uint32_t instructionScratch = 0;
 
     // Emit the instruction mask in the scratch space.
     // The offset doesn't matter: it will be fixed up later.
     vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
 
     // Add the entry to the pool, fix up the LDR imm19 offset,
     // and add the completed instruction to the buffer.
-    return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
-                      literalAddr);
+    return allocLiteralLoadEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
+                                 literalAddr);
 }
 
 void
 MacroAssemblerCompat::loadPrivate(const Address& src, Register dest)
 {
     loadPtr(src, dest);
     asMasm().lshiftPtr(Imm32(1), dest);
 }
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -666,17 +666,17 @@ class MacroAssemblerCompat : public vixl
     void jump(Label* label) {
         B(label);
     }
     void jump(JitCode* code) {
         branch(code);
     }
     void jump(TrampolinePtr code) {
         syncStackPtr();
-        BufferOffset loc = b(-1); // The jump target will be patched by executableCopy().
+        BufferOffset loc = b(-1, LabelDoc()); // The jump target will be patched by executableCopy().
         addPendingJump(loc, ImmPtr(code.value), Relocation::HARDCODED);
     }
     void jump(RepatchLabel* label) {
         MOZ_CRASH("jump (repatchlabel)");
     }
     void jump(Register reg) {
         Br(ARMRegister(reg, 64));
     }
@@ -1261,48 +1261,52 @@ class MacroAssemblerCompat : public vixl
         B(dest, cond);
     }
 
     void branch(Condition cond, Label* label) {
         B(label, cond);
     }
     void branch(JitCode* target) {
         syncStackPtr();
-        BufferOffset loc = b(-1); // The jump target will be patched by executableCopy().
+        BufferOffset loc = b(-1, LabelDoc()); // The jump target will be patched by executableCopy().
         addPendingJump(loc, ImmPtr(target->raw()), Relocation::JITCODE);
     }
 
-    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always,
-                                 Label* documentation = nullptr)
+    CodeOffsetJump jumpWithPatch(RepatchLabel* label, Condition cond = Always, Label* documentation = nullptr)
     {
+#ifdef JS_DISASM_ARM64
+        LabelDoc doc = spew_.refLabel(documentation);
+#else
+        LabelDoc doc;
+#endif
         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();
             load_bo = immPool64(scratch64, (uint64_t)label, &pe);
         }
 
         MOZ_ASSERT(!label->bound());
         if (cond != Always) {
             Label notTaken;
             B(&notTaken, Assembler::InvertCondition(cond));
-            branch_bo = b(-1);
+            branch_bo = b(-1, doc);
             bind(&notTaken);
         } else {
             nop();
-            branch_bo = b(-1);
+            branch_bo = b(-1, doc);
         }
         label->use(branch_bo.getOffset());
         return CodeOffsetJump(load_bo.getOffset(), pe.index());
     }
-    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr) {
+    CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation) {
         return jumpWithPatch(label, Always, documentation);
     }
 
     void compareDouble(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs) {
         Fcmp(ARMFPRegister(lhs, 64), ARMFPRegister(rhs, 64));
     }
 
     void compareFloat(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs) {
--- a/js/src/jit/arm64/vixl/Assembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Assembler-vixl.cpp
@@ -1700,18 +1700,18 @@ void Assembler::LoadStoreStruct(const VR
                                 NEONLoadStoreMultiStructOp op) {
   LoadStoreStructVerify(vt, addr, op);
   VIXL_ASSERT(vt.IsVector() || vt.Is1D());
   Emit(op | LoadStoreStructAddrModeField(addr) | LSVFormat(vt) | Rt(vt));
 }
 
 
 void Assembler::LoadStoreStructSingleAllLanes(const VRegister& vt,
-                                      const MemOperand& addr,
-                                      NEONLoadStoreSingleStructOp op) {
+					      const MemOperand& addr,
+					      NEONLoadStoreSingleStructOp op) {
   LoadStoreStructVerify(vt, addr, op);
   Emit(op | LoadStoreStructAddrModeField(addr) | LSVFormat(vt) | Rt(vt));
 }
 
 
 void Assembler::ld1(const VRegister& vt,
                     const MemOperand& src) {
   LoadStoreStruct(vt, src, NEON_LD1_1v);
--- a/js/src/jit/arm64/vixl/Assembler-vixl.h
+++ b/js/src/jit/arm64/vixl/Assembler-vixl.h
@@ -30,24 +30,28 @@
 #include "jit/arm64/vixl/Globals-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 #include "jit/arm64/vixl/MozBaseAssembler-vixl.h"
 #include "jit/arm64/vixl/Utils-vixl.h"
 
 #include "jit/JitSpewer.h"
 
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
 namespace vixl {
 
 using js::jit::BufferOffset;
 using js::jit::Label;
 using js::jit::Address;
 using js::jit::BaseIndex;
+using js::jit::DisassemblerSpew;
+
+using LabelDoc = DisassemblerSpew::LabelDoc;
 
 typedef uint64_t RegList;
 static const int kRegListSizeInBits = sizeof(RegList) * 8;
 
 
 // Registers.
 
 // Some CPURegister methods can return Register or VRegister types, so we need
@@ -966,42 +970,42 @@ class Assembler : public MozBaseAssemble
 
   // Unconditional branch to label.
   BufferOffset b(Label* label);
 
   // Conditional branch to label.
   BufferOffset b(Label* label, Condition cond);
 
   // Unconditional branch to PC offset.
-  BufferOffset b(int imm26);
+  BufferOffset b(int imm26, const LabelDoc& doc);
   static void b(Instruction* at, int imm26);
 
   // Conditional branch to PC offset.
-  BufferOffset b(int imm19, Condition cond);
+  BufferOffset b(int imm19, Condition cond, const LabelDoc& doc);
   static void b(Instruction*at, int imm19, Condition cond);
 
   // Branch with link to label.
   void bl(Label* label);
 
   // Branch with link to PC offset.
-  void bl(int imm26);
+  void bl(int imm26, const LabelDoc& doc);
   static void bl(Instruction* at, int imm26);
 
   // Compare and branch to label if zero.
   void cbz(const Register& rt, Label* label);
 
   // Compare and branch to PC offset if zero.
-  void cbz(const Register& rt, int imm19);
+  void cbz(const Register& rt, int imm19, const LabelDoc& doc);
   static void cbz(Instruction* at, const Register& rt, int imm19);
 
   // Compare and branch to label if not zero.
   void cbnz(const Register& rt, Label* label);
 
   // Compare and branch to PC offset if not zero.
-  void cbnz(const Register& rt, int imm19);
+  void cbnz(const Register& rt, int imm19, const LabelDoc& doc);
   static void cbnz(Instruction* at, const Register& rt, int imm19);
 
   // Table lookup from one register.
   void tbl(const VRegister& vd,
            const VRegister& vn,
            const VRegister& vm);
 
   // Table lookup from two registers.
@@ -1050,42 +1054,42 @@ class Assembler : public MozBaseAssemble
            const VRegister& vn3,
            const VRegister& vn4,
            const VRegister& vm);
 
   // Test bit and branch to label if zero.
   void tbz(const Register& rt, unsigned bit_pos, Label* label);
 
   // Test bit and branch to PC offset if zero.
-  void tbz(const Register& rt, unsigned bit_pos, int imm14);
+  void tbz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc);
   static void tbz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14);
 
   // Test bit and branch to label if not zero.
   void tbnz(const Register& rt, unsigned bit_pos, Label* label);
 
   // Test bit and branch to PC offset if not zero.
-  void tbnz(const Register& rt, unsigned bit_pos, int imm14);
+  void tbnz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc);
   static void tbnz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14);
 
   // Address calculation instructions.
   // Calculate a PC-relative address. Unlike for branches the offset in adr is
   // unscaled (i.e. the result can be unaligned).
 
   // Calculate the address of a label.
   void adr(const Register& rd, Label* label);
 
   // Calculate the address of a PC offset.
-  void adr(const Register& rd, int imm21);
+  void adr(const Register& rd, int imm21, const LabelDoc& doc);
   static void adr(Instruction* at, const Register& rd, int imm21);
 
   // Calculate the page address of a label.
   void adrp(const Register& rd, Label* label);
 
   // Calculate the page address of a PC offset.
-  void adrp(const Register& rd, int imm21);
+  void adrp(const Register& rd, int imm21, const LabelDoc& doc);
   static void adrp(Instruction* at, const Register& rd, int imm21);
 
   // Data Processing instructions.
   // Add.
   void add(const Register& rd,
            const Register& rn,
            const Operand& operand);
 
--- a/js/src/jit/arm64/vixl/Disasm-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.cpp
@@ -2746,20 +2746,24 @@ int64_t Disassembler::CodeRelativeAddres
   return reinterpret_cast<intptr_t>(addr) + code_address_offset();
 }
 
 
 void Disassembler::Format(const Instruction* instr, const char* mnemonic,
                           const char* format) {
   VIXL_ASSERT(mnemonic != NULL);
   ResetOutput();
+  uint32_t pos = buffer_pos_;
   Substitute(instr, mnemonic);
   if (format != NULL) {
-    VIXL_ASSERT(buffer_pos_ < buffer_size_);
-    buffer_[buffer_pos_++] = ' ';
+    uint32_t spaces = buffer_pos_ - pos < 8 ? 8 - (buffer_pos_ - pos) : 1;
+    while (spaces--) {
+      VIXL_ASSERT(buffer_pos_ < buffer_size_);
+      buffer_[buffer_pos_++] = ' ';
+    }
     Substitute(instr, format);
   }
   VIXL_ASSERT(buffer_pos_ < buffer_size_);
   buffer_[buffer_pos_] = 0;
   ProcessOutput(instr);
 }
 
 
@@ -3480,9 +3484,18 @@ void Disassembler::AppendToOutput(const 
 
 void PrintDisassembler::ProcessOutput(const Instruction* instr) {
   fprintf(stream_, "0x%016" PRIx64 "  %08" PRIx32 "\t\t%s\n",
           reinterpret_cast<uint64_t>(instr),
           instr->InstructionBits(),
           GetOutput());
 }
 
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr)
+{
+    vixl::Disassembler disasm(buffer, bufsize-1);
+    vixl::Decoder decoder;
+    decoder.AppendVisitor(&disasm);
+    decoder.Decode(instr);
+    buffer[bufsize-1] = 0;      // Just to be safe
+}
+
 }  // namespace vixl
--- a/js/src/jit/arm64/vixl/Disasm-vixl.h
+++ b/js/src/jit/arm64/vixl/Disasm-vixl.h
@@ -167,11 +167,14 @@ class PrintDisassembler: public Disassem
   explicit PrintDisassembler(FILE* stream) : stream_(stream) { }
 
  protected:
   virtual void ProcessOutput(const Instruction* instr) override;
 
  private:
   FILE *stream_;
 };
+
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr);
+
 }  // namespace vixl
 
 #endif  // VIXL_A64_DISASM_A64_H
--- a/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozAssembler-vixl.cpp
@@ -26,16 +26,17 @@
 
 #include "jsutil.h"
 
 #include "jit/arm64/vixl/Assembler-vixl.h"
 #include "jit/Label.h"
 
 namespace vixl {
 
+using LabelDoc = js::jit::DisassemblerSpew::LabelDoc;
 
 // Assembler
 void Assembler::FinalizeCode() {
 #ifdef DEBUG
   finalized_ = true;
 #endif
 }
 
@@ -191,179 +192,188 @@ ptrdiff_t MozBaseAssembler::LinkAndGetIn
                                                           Label* label) {
   return LinkAndGetOffsetTo(branch, branchRange, kInstructionSizeLog2, label);
 }
 
 ptrdiff_t MozBaseAssembler::LinkAndGetPageOffsetTo(BufferOffset branch, Label* label) {
   return LinkAndGetOffsetTo(branch, UncondBranchRangeType, kPageSizeLog2, label);
 }
 
-BufferOffset Assembler::b(int imm26) {
-  return EmitBranch(B | ImmUncondBranch(imm26));
+BufferOffset Assembler::b(int imm26, const LabelDoc& doc) {
+  return EmitBranch(B | ImmUncondBranch(imm26), doc);
 }
 
 
 void Assembler::b(Instruction* at, int imm26) {
   return EmitBranch(at, B | ImmUncondBranch(imm26));
 }
 
 
-BufferOffset Assembler::b(int imm19, Condition cond) {
-  return EmitBranch(B_cond | ImmCondBranch(imm19) | cond);
+BufferOffset Assembler::b(int imm19, Condition cond, const LabelDoc& doc) {
+  return EmitBranch(B_cond | ImmCondBranch(imm19) | cond, doc);
 }
 
 
 void Assembler::b(Instruction* at, int imm19, Condition cond) {
   EmitBranch(at, B_cond | ImmCondBranch(imm19) | cond);
 }
 
 
 BufferOffset Assembler::b(Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label), doc);
 }
 
 
 BufferOffset Assembler::b(Label* label, Condition cond) {
   // Encode the relative offset from the inserted branch to the label.
-  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), cond);
+  LabelDoc doc = refLabel(label);
+  return b(LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), cond, doc);
 }
 
 void Assembler::br(Instruction* at, const Register& xn) {
   VIXL_ASSERT(xn.Is64Bits());
   // No need for EmitBranch(): no immediate offset needs fixing.
   Emit(at, BR | Rn(xn));
 }
 
 
 void Assembler::blr(Instruction* at, const Register& xn) {
   VIXL_ASSERT(xn.Is64Bits());
   // No need for EmitBranch(): no immediate offset needs fixing.
   Emit(at, BLR | Rn(xn));
 }
 
 
-void Assembler::bl(int imm26) {
-  EmitBranch(BL | ImmUncondBranch(imm26));
+void Assembler::bl(int imm26, const LabelDoc& doc) {
+  EmitBranch(BL | ImmUncondBranch(imm26), doc);
 }
 
 
 void Assembler::bl(Instruction* at, int imm26) {
   EmitBranch(at, BL | ImmUncondBranch(imm26));
 }
 
 
 void Assembler::bl(Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return bl(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return bl(LinkAndGetInstructionOffsetTo(nextInstrOffset(), UncondBranchRangeType, label), doc);
 }
 
 
-void Assembler::cbz(const Register& rt, int imm19) {
-  EmitBranch(SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt));
+void Assembler::cbz(const Register& rt, int imm19, const LabelDoc& doc) {
+  EmitBranch(SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt), doc);
 }
 
 
 void Assembler::cbz(Instruction* at, const Register& rt, int imm19) {
   EmitBranch(at, SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt));
 }
 
 
 void Assembler::cbz(const Register& rt, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return cbz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return cbz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), doc);
 }
 
 
-void Assembler::cbnz(const Register& rt, int imm19) {
-  EmitBranch(SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt));
+void Assembler::cbnz(const Register& rt, int imm19, const LabelDoc& doc) {
+  EmitBranch(SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt), doc);
 }
 
 
 void Assembler::cbnz(Instruction* at, const Register& rt, int imm19) {
   EmitBranch(at, SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt));
 }
 
 
 void Assembler::cbnz(const Register& rt, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return cbnz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return cbnz(rt, LinkAndGetInstructionOffsetTo(nextInstrOffset(), CondBranchRangeType, label), doc);
 }
 
 
-void Assembler::tbz(const Register& rt, unsigned bit_pos, int imm14) {
+void Assembler::tbz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
-  EmitBranch(TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
+  EmitBranch(TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt), doc);
 }
 
 
 void Assembler::tbz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
   EmitBranch(at, TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
 }
 
 
 void Assembler::tbz(const Register& rt, unsigned bit_pos, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return tbz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return tbz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label), doc);
 }
 
 
-void Assembler::tbnz(const Register& rt, unsigned bit_pos, int imm14) {
+void Assembler::tbnz(const Register& rt, unsigned bit_pos, int imm14, const LabelDoc& doc) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
-  EmitBranch(TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
+  EmitBranch(TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt), doc);
 }
 
 
 void Assembler::tbnz(Instruction* at, const Register& rt, unsigned bit_pos, int imm14) {
   VIXL_ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSize)));
   EmitBranch(at, TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt));
 }
 
 
 void Assembler::tbnz(const Register& rt, unsigned bit_pos, Label* label) {
   // Encode the relative offset from the inserted branch to the label.
-  return tbnz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label));
+  LabelDoc doc = refLabel(label);
+  return tbnz(rt, bit_pos, LinkAndGetInstructionOffsetTo(nextInstrOffset(), TestBranchRangeType, label), doc);
 }
 
 
-void Assembler::adr(const Register& rd, int imm21) {
+void Assembler::adr(const Register& rd, int imm21, const LabelDoc& doc) {
   VIXL_ASSERT(rd.Is64Bits());
-  EmitBranch(ADR | ImmPCRelAddress(imm21) | Rd(rd));
+  EmitBranch(ADR | ImmPCRelAddress(imm21) | Rd(rd), doc);
 }
 
 
 void Assembler::adr(Instruction* at, const Register& rd, int imm21) {
   VIXL_ASSERT(rd.Is64Bits());
   EmitBranch(at, ADR | ImmPCRelAddress(imm21) | Rd(rd));
 }
 
 
 void Assembler::adr(const Register& rd, Label* label) {
   // Encode the relative offset from the inserted adr to the label.
-  return adr(rd, LinkAndGetByteOffsetTo(nextInstrOffset(), label));
+  LabelDoc doc = refLabel(label);
+  return adr(rd, LinkAndGetByteOffsetTo(nextInstrOffset(), label), doc);
 }
 
 
-void Assembler::adrp(const Register& rd, int imm21) {
+void Assembler::adrp(const Register& rd, int imm21, const LabelDoc& doc) {
   VIXL_ASSERT(rd.Is64Bits());
-  EmitBranch(ADRP | ImmPCRelAddress(imm21) | Rd(rd));
+  EmitBranch(ADRP | ImmPCRelAddress(imm21) | Rd(rd), doc);
 }
 
 
 void Assembler::adrp(Instruction* at, const Register& rd, int imm21) {
   VIXL_ASSERT(rd.Is64Bits());
   EmitBranch(at, ADRP | ImmPCRelAddress(imm21) | Rd(rd));
 }
 
 
 void Assembler::adrp(const Register& rd, Label* label) {
   VIXL_ASSERT(AllowPageOffsetDependentCode());
   // Encode the relative offset from the inserted adr to the label.
-  return adrp(rd, LinkAndGetPageOffsetTo(nextInstrOffset(), label));
+  LabelDoc doc = refLabel(label);
+  return adrp(rd, LinkAndGetPageOffsetTo(nextInstrOffset(), label), doc);
 }
 
 
 BufferOffset Assembler::ands(const Register& rd, const Register& rn, const Operand& operand) {
   return Logical(rd, rn, operand, ANDS);
 }
 
 
--- a/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
+++ b/js/src/jit/arm64/vixl/MozBaseAssembler-vixl.h
@@ -26,23 +26,31 @@
 
 #ifndef jit_arm64_vixl_MozBaseAssembler_vixl_h
 #define jit_arm64_vixl_MozBaseAssembler_vixl_h
 
 #include "jit/arm64/vixl/Constants-vixl.h"
 #include "jit/arm64/vixl/Instructions-vixl.h"
 
 #include "jit/shared/Assembler-shared.h"
+#include "jit/shared/Disassembler-shared.h"
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
 namespace vixl {
 
 
 using js::jit::BufferOffset;
+using js::jit::DisassemblerSpew;
 
+using LabelDoc = DisassemblerSpew::LabelDoc;
+using LiteralDoc = DisassemblerSpew::LiteralDoc;
+
+#ifdef JS_DISASM_ARM64
+void DisassembleInstruction(char* buffer, size_t bufsize, const Instruction* instr);
+#endif
 
 class MozBaseAssembler;
 typedef js::jit::AssemblerBufferWithConstantPools<1024, 4, Instruction, MozBaseAssembler,
                                                   NumShortBranchRangeTypes> ARMBuffer;
 
 // Base class for vixl::Assembler, for isolating Moz-specific changes to VIXL.
 class MozBaseAssembler : public js::jit::AssemblerShared {
   // Buffer initialization constants.
@@ -50,27 +58,44 @@ class MozBaseAssembler : public js::jit:
   static const unsigned BufferHeaderSize = 1;
   static const size_t   BufferCodeAlignment = 8;
   static const size_t   BufferMaxPoolOffset = 1024;
   static const unsigned BufferPCBias = 0;
   static const uint32_t BufferAlignmentFillInstruction = BRK | (0xdead << ImmException_offset);
   static const uint32_t BufferNopFillInstruction = HINT | (31 << Rt_offset);
   static const unsigned BufferNumDebugNopsToInsert = 0;
 
+#ifdef JS_DISASM_ARM64
+  static constexpr const char* const InstrIndent = "        ";
+  static constexpr const char* const LabelIndent = "          ";
+  static constexpr const char* const TargetIndent = "                    ";
+#endif
+
  public:
   MozBaseAssembler()
     : armbuffer_(BufferGuardSize,
                  BufferHeaderSize,
                  BufferCodeAlignment,
                  BufferMaxPoolOffset,
                  BufferPCBias,
                  BufferAlignmentFillInstruction,
                  BufferNopFillInstruction,
                  BufferNumDebugNopsToInsert)
-  { }
+  {
+#ifdef JS_DISASM_ARM64
+      spew_.setLabelIndent(LabelIndent);
+      spew_.setTargetIndent(TargetIndent);
+#endif
+}
+  ~MozBaseAssembler()
+  {
+#ifdef JS_DISASM_ARM64
+      spew_.spewOrphans();
+#endif
+  }
 
  public:
   // Helper function for use with the ARMBuffer.
   // The MacroAssembler must create an AutoJitContextAlloc before initializing the buffer.
   void initWithAllocator() {
     armbuffer_.initWithAllocator();
   }
 
@@ -97,36 +122,131 @@ class MozBaseAssembler : public js::jit:
   // Get the next usable buffer offset. Note that a constant pool may be placed
   // here before the next instruction is emitted.
   BufferOffset nextOffset() const {
     return armbuffer_.nextOffset();
   }
 
   // Allocate memory in the buffer by forwarding to armbuffer_.
   // Propagate OOM errors.
-  BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
-                          uint8_t* inst, uint8_t* data,
-                          ARMBuffer::PoolEntry* pe = nullptr)
+  BufferOffset allocLiteralLoadEntry(size_t numInst, unsigned numPoolEntries,
+				     uint8_t* inst, uint8_t* data,
+				     const LiteralDoc& doc = LiteralDoc(),
+				     ARMBuffer::PoolEntry* pe = nullptr)
   {
+    MOZ_ASSERT(inst);
+    MOZ_ASSERT(numInst == 1);	/* If not, then fix disassembly */
     BufferOffset offset = armbuffer_.allocEntry(numInst, numPoolEntries, inst,
                                                 data, pe);
     propagateOOM(offset.assigned());
+#ifdef JS_DISASM_ARM64
+    Instruction* instruction = armbuffer_.getInstOrNull(offset);
+    if (instruction)
+        spewLiteralLoad(reinterpret_cast<vixl::Instruction*>(instruction), doc);
+#endif
     return offset;
   }
 
+#ifdef JS_DISASM_ARM64
+  DisassemblerSpew spew_;
+
+  void spew(const vixl::Instruction* instr) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+    spew_.spew("%08" PRIx32 "%s%s", instr->InstructionBits(), InstrIndent, buffer);
+  }
+
+  void spewBranch(const vixl::Instruction* instr, const LabelDoc& target) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+
+    char labelBuf[128];
+    labelBuf[0] = 0;
+
+    bool hasTarget = target.valid;
+    if (!hasTarget)
+      snprintf(labelBuf, sizeof(labelBuf), "-> (link-time target)");
+
+    if (instr->IsImmBranch() && hasTarget) {
+      // 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 /.*#.*/.  Strip the # and later.
+      size_t i;
+      const size_t BUFLEN = sizeof(buffer)-1;
+      for ( i=0 ; i < BUFLEN && buffer[i] && buffer[i] != '#' ; i++ )
+	;
+      buffer[i] = 0;
+
+      snprintf(labelBuf, sizeof(labelBuf), "-> %d%s", target.doc, !target.bound ? "f" : "");
+      hasTarget = false;
+    }
+
+    spew_.spew("%08" PRIx32 "%s%s%s", instr->InstructionBits(), InstrIndent, buffer, labelBuf);
+
+    if (hasTarget)
+      spew_.spewRef(target);
+  }
+
+  void spewLiteralLoad(const vixl::Instruction* instr, const LiteralDoc& doc) {
+    if (spew_.isDisabled() || !instr)
+      return;
+
+    char buffer[2048];
+    DisassembleInstruction(buffer, sizeof(buffer), instr);
+
+    char litbuf[2048];
+    spew_.formatLiteral(doc, litbuf, sizeof(litbuf));
+
+    // The instruction will have the form /^.*pc\+0/ followed by junk that we
+    // don't need; try to strip it.
+
+    char *probe = strstr(buffer, "pc+0");
+    if (probe)
+      *(probe + 4) = 0;
+    spew_.spew("%08" PRIx32 "%s%s    ; .const %s", instr->InstructionBits(), InstrIndent, buffer, litbuf);
+  }
+
+  LabelDoc refLabel(Label* label) {
+    if (spew_.isDisabled())
+      return LabelDoc();
+
+    return spew_.refLabel(label);
+  }
+#else
+  LabelDoc refLabel(js::jit::Label*) {
+      return LabelDoc();
+  }
+#endif
+
   // Emit the instruction, returning its offset.
   BufferOffset Emit(Instr instruction, bool isBranch = false) {
     JS_STATIC_ASSERT(sizeof(instruction) == kInstructionSize);
     // TODO: isBranch is obsolete and should be removed.
     (void)isBranch;
-    return armbuffer_.putInt(*(uint32_t*)(&instruction));
+    BufferOffset offs = armbuffer_.putInt(*(uint32_t*)(&instruction));
+#ifdef JS_DISASM_ARM64
+    if (!isBranch)
+	spew(armbuffer_.getInstOrNull(offs));
+#endif
+    return offs;
   }
 
-  BufferOffset EmitBranch(Instr instruction) {
-    return Emit(instruction, true);
+  BufferOffset EmitBranch(Instr instruction, const LabelDoc& doc) {
+    BufferOffset offs = Emit(instruction, true);
+#ifdef JS_DISASM_ARM64
+    spewBranch(armbuffer_.getInstOrNull(offs), doc);
+#endif
+    return offs;
   }
 
  public:
   // Emit the instruction at |at|.
   static void Emit(Instruction* at, Instr instruction) {
     JS_STATIC_ASSERT(sizeof(instruction) == kInstructionSize);
     memcpy(at, &instruction, sizeof(instruction));
   }
new file mode 100644
--- /dev/null
+++ b/js/src/jit/shared/Disassembler-shared.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/shared/Disassembler-shared.h"
+
+#include "jsprf.h"
+
+#include "jit/JitSpewer.h"
+
+using namespace js::jit;
+
+#ifdef JS_DISASM_SUPPORTED
+// See comments in spew(), below.
+mozilla::Atomic<uint32_t> DisassemblerSpew::live_(0);
+#endif
+
+DisassemblerSpew::DisassemblerSpew()
+  : printer_(nullptr)
+#ifdef JS_DISASM_SUPPORTED
+    ,
+    labelIndent_(""),
+    targetIndent_(""),
+    spewNext_(1000),
+    nodes_(nullptr)
+#endif
+{
+#ifdef JS_DISASM_SUPPORTED
+    live_++;
+#endif
+}
+
+DisassemblerSpew::~DisassemblerSpew()
+{
+#ifdef JS_DISASM_SUPPORTED
+    Node* p = nodes_;
+    while (p) {
+        Node* victim = p;
+        p = p->next;
+        js_free(victim);
+    }
+    live_--;
+#endif
+}
+
+void
+DisassemblerSpew::setPrinter(Sprinter* printer)
+{
+    printer_ = printer;
+}
+
+bool
+DisassemblerSpew::isDisabled()
+{
+    return !(JitSpewEnabled(JitSpew_Codegen) || printer_);
+}
+
+void
+DisassemblerSpew::spew(const char* fmt, ...)
+{
+    // Nested assemblers are handled by prefixing the output with '>..> ' where
+    // the number of '>' is the nesting level, and the outermost assembler is
+    // taken as being at nesting level zero (and does not require the trailing
+    // space character).  This markup disambiguates eg the output of an IC
+    // compilation that happens as a subtask of a normal compilation from the
+    // output of the normal compilation.
+    //
+    // We track the nesting level globally, on the assumption that anyone
+    // wanting to look at disassembly is running with --no-threads.  If this
+    // turns out to be wrong then live_ can be made thread-local.
+
+#ifdef JS_DISASM_SUPPORTED
+    char fmt2[1024];
+    MOZ_RELEASE_ASSERT(sizeof(fmt2) >= strlen(fmt) + live_ + 1);
+    uint32_t i;
+    for (i = 0; i < live_-1; i++ )
+        fmt2[i] = '>';
+    if (live_ > 1)
+        fmt2[i++] = ' ';
+    strcpy(fmt2 + i, fmt);
+#else
+    const char* fmt2 = fmt;
+#endif
+
+    va_list args;
+    va_start(args, fmt);
+    spewVA(fmt2, args);
+    va_end(args);
+}
+
+void
+DisassemblerSpew::spewVA(const char* fmt, va_list va)
+{
+    if (printer_) {
+        printer_->vprintf(fmt, va);
+        printer_->put("\n");
+    }
+    js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va);
+}
+
+#ifdef JS_DISASM_SUPPORTED
+
+void
+DisassemblerSpew::setLabelIndent(const char* s)
+{
+    labelIndent_ = s;
+}
+
+void
+DisassemblerSpew::setTargetIndent(const char* s)
+{
+    targetIndent_ = s;
+}
+
+DisassemblerSpew::LabelDoc
+DisassemblerSpew::refLabel(const Label* l)
+{
+    return l ? LabelDoc(internalResolve(l), l->bound()) : LabelDoc();
+}
+
+void
+DisassemblerSpew::spewRef(const LabelDoc& target)
+{
+    if (isDisabled())
+        return;
+    if (!target.valid)
+        return;
+    spew("%s-> %d%s", targetIndent_, target.doc, !target.bound ? "f" : "");
+}
+
+void
+DisassemblerSpew::spewBind(const Label* label)
+{
+    if (isDisabled())
+        return;
+    uint32_t v = internalResolve(label);
+    Node* probe = lookup(label);
+    if (probe)
+        probe->bound = true;
+    spew("%s%d:", labelIndent_, v);
+}
+
+void
+DisassemblerSpew::spewRetarget(const Label* label, const Label* target)
+{
+    if (isDisabled())
+        return;
+    LabelDoc labelDoc = LabelDoc(internalResolve(label), label->bound());
+    LabelDoc targetDoc = LabelDoc(internalResolve(target), target->bound());
+    Node* probe = lookup(label);
+    if (probe)
+        probe->bound = true;
+    spew("%s%d: .retarget -> %d%s",
+         labelIndent_, labelDoc.doc, targetDoc.doc, !targetDoc.bound ? "f" : "");
+}
+
+void
+DisassemblerSpew::formatLiteral(const LiteralDoc& doc, char* buffer, size_t bufsize)
+{
+    switch (doc.type) {
+      case LiteralDoc::Type::Patchable:
+        snprintf(buffer, bufsize, "patchable");
+        break;
+      case LiteralDoc::Type::I32:
+        snprintf(buffer, bufsize, "%d", doc.value.i32);
+        break;
+      case LiteralDoc::Type::U32:
+        snprintf(buffer, bufsize, "%u", doc.value.u32);
+        break;
+      case LiteralDoc::Type::I64:
+        snprintf(buffer, bufsize, "%" PRIi64, doc.value.i64);
+        break;
+      case LiteralDoc::Type::U64:
+        snprintf(buffer, bufsize, "%" PRIu64, doc.value.u64);
+        break;
+      case LiteralDoc::Type::F32:
+        snprintf(buffer, bufsize, "%g", doc.value.f32);
+        break;
+      case LiteralDoc::Type::F64:
+        snprintf(buffer, bufsize, "%g", doc.value.f64);
+        break;
+      default:
+        MOZ_CRASH();
+    }
+}
+
+void
+DisassemblerSpew::spewOrphans()
+{
+    for (Node* p = nodes_; p; p = p->next) {
+        if (!p->bound)
+            spew("%s%d:    ; .orphan", labelIndent_, p->value);
+    }
+}
+
+uint32_t
+DisassemblerSpew::internalResolve(const Label* l)
+{
+    // Note, internalResolve 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.  In that case,
+    // internalResolve(l) will not necessarily create a binding for l!
+    // Consequently a subsequent lookup(l) may still return null.
+    return l->used() || l->bound() ? probe(l) : define(l);
+}
+
+uint32_t
+DisassemblerSpew::probe(const Label* l)
+{
+    Node* n = lookup(l);
+    return n ? n->value : 0;
+}
+
+uint32_t
+DisassemblerSpew::define(const Label* l)
+{
+    remove(l);
+    uint32_t value = spewNext_++;
+    if (!add(l, value))
+        return 0;
+    return value;
+}
+
+DisassemblerSpew::Node*
+DisassemblerSpew::lookup(const Label* key)
+{
+    Node* p;
+    for (p = nodes_; p && p->key != key; p = p->next)
+        ;
+    return p;
+}
+
+DisassemblerSpew::Node*
+DisassemblerSpew::add(const Label* key, uint32_t value)
+{
+    MOZ_ASSERT(!lookup(key));
+    Node* node = (Node*)js_malloc(sizeof(Node));
+    if (node) {
+        node->key = key;
+        node->value = value;
+        node->bound = false;
+        node->next = nodes_;
+        nodes_ = node;
+    }
+    return node;
+}
+
+bool
+DisassemblerSpew::remove(const Label* key)
+{
+    // We do not require that there is a node matching the 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;
+}
+
+#else
+
+DisassemblerSpew::LabelDoc
+DisassemblerSpew::refLabel(const Label* l)
+{
+    return LabelDoc();
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/js/src/jit/shared/Disassembler-shared.h
@@ -0,0 +1,183 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef jit_shared_Disassembler_shared_h
+#define jit_shared_Disassembler_shared_h
+
+#include "mozilla/Atomics.h"
+
+#include "jit/Label.h"
+#ifdef JS_DISASM_SUPPORTED
+# include "jit/shared/IonAssemblerBuffer.h"
+#endif
+
+using js::jit::Label;
+using js::Sprinter;
+
+#if defined(JS_DISASM_ARM) || defined(JS_DISASM_ARM64)
+#  define JS_DISASM_SUPPORTED
+#endif
+
+namespace js {
+namespace jit {
+
+// A wrapper around spew/disassembly functionality.  The disassembler is built
+// on a per-instruction disassembler (as in our ARM, ARM64 back-ends) and
+// formats labels with meaningful names and literals with meaningful values, if
+// the assembler creates documentation (with provided helpers) at appropriate
+// points.
+
+class DisassemblerSpew
+{
+#ifdef JS_DISASM_SUPPORTED
+    struct Node
+    {
+        const Label* key;       // Never dereferenced, only used for its value
+        uint32_t value;         // The printable label value
+        bool bound;             // If the label has been seen by spewBind()
+        Node* next;
+    };
+
+    Node* lookup(const Label* key);
+    Node* add(const Label* key, uint32_t value);
+    bool remove(const Label* key);
+
+    uint32_t probe(const Label* l);
+    uint32_t define(const Label* l);
+    uint32_t internalResolve(const Label* l);
+#endif
+
+    void spewVA(const char* fmt, va_list args) MOZ_FORMAT_PRINTF(2, 0);
+
+  public:
+    DisassemblerSpew();
+    ~DisassemblerSpew();
+
+#ifdef JS_DISASM_SUPPORTED
+    // Set indentation strings.  The spewer retains a reference to s.
+    void setLabelIndent(const char* s);
+    void setTargetIndent(const char* s);
+#endif
+
+    // Set the spew printer, which will always be used if it is set, regardless
+    // of whether the system spew channel is enabled or not.  The spewer retains
+    // a reference to sp.
+    void setPrinter(Sprinter* sp);
+
+    // Return true if disassembly spew is disabled and no additional printer is
+    // set.
+    bool isDisabled();
+
+    // Format and print text on the spew channel; output is suppressed if spew
+    // is disabled.  The output is not indented, and is terminated by a newline.
+    void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
+
+    // Documentation for a label reference.
+    struct LabelDoc
+    {
+#ifdef JS_DISASM_SUPPORTED
+	LabelDoc() : doc(0), bound(false), valid(false) {}
+	LabelDoc(uint32_t doc, bool bound) : doc(doc), bound(bound), valid(true) {}
+	const uint32_t doc;
+	const bool bound;
+	const bool valid;
+#else
+	LabelDoc() {}
+	LabelDoc(uint32_t, bool) {}
+#endif
+    };
+
+    // Documentation for a literal load.
+    struct LiteralDoc
+    {
+#ifdef JS_DISASM_SUPPORTED
+        enum class Type { Patchable, I32, U32, I64, U64, F32, F64 };
+        const Type type;
+        union {
+            int32_t  i32;
+            uint32_t u32;
+            int64_t  i64;
+            uint64_t u64;
+            float    f32;
+            double   f64;
+        } value;
+        LiteralDoc() : type(Type::Patchable) {}
+        explicit LiteralDoc(int32_t v) : type(Type::I32) { value.i32 = v; }
+        explicit LiteralDoc(uint32_t v) : type(Type::U32) { value.u32 = v; }
+        explicit LiteralDoc(int64_t v) : type(Type::I64) { value.i64 = v; }
+        explicit LiteralDoc(uint64_t v) : type(Type::U64) { value.u64 = v; }
+        explicit LiteralDoc(float v) : type(Type::F32) { value.f32 = v; }
+        explicit LiteralDoc(double v) : type(Type::F64) { value.f64 = v; }
+#else
+        LiteralDoc() {}
+        explicit LiteralDoc(int32_t) {}
+        explicit LiteralDoc(uint32_t) {}
+        explicit LiteralDoc(int64_t) {}
+        explicit LiteralDoc(uint64_t) {}
+        explicit LiteralDoc(float) {}
+        explicit LiteralDoc(double) {}
+#endif
+    };
+
+    // Reference a label, resolving it to a printable representation.
+    //
+    // NOTE: The printable representation depends on the state of the label, so
+    // if we call resolve() when emitting & disassembling a branch instruction
+    // then it should be called before the label becomes Used, if emitting the
+    // branch can change the label's state.
+    //
+    // If the disassembler is not defined this returns a structure that is
+    // marked not valid.
+    LabelDoc refLabel(const Label* l);
+
+#ifdef JS_DISASM_SUPPORTED
+    // Spew the label information previously gathered by refLabel(), at a point
+    // where the label is referenced.  The output is indented by targetIndent_
+    // and terminated by a newline.
+    void spewRef(const LabelDoc& target);
+
+    // Spew the label at the point where the label is bound.  The output is
+    // indented by labelIndent_ and terminated by a newline.
+    void spewBind(const Label* label);
+
+    // Spew a retarget directive at the point where the retarget is recorded.
+    // The output is indented by labelIndent_ and terminated by a newline.
+    void spewRetarget(const Label* label, const Label* target);
+
+    // Format a literal value into the buffer.  The buffer is always
+    // NUL-terminated even if this chops the formatted value.
+    void formatLiteral(const LiteralDoc& doc, char* buffer, size_t bufsize);
+
+    // Print any unbound labels, one per line, with normal label indent and with
+    // a comment indicating the label is not defined.  Labels can be referenced
+    // but unbound in some legitimate cases, normally for traps.  Printing them
+    // reduces confusion.
+    void spewOrphans();
+#endif
+
+  private:
+    Sprinter* printer_;
+#ifdef JS_DISASM_SUPPORTED
+    const char* labelIndent_;
+    const char* targetIndent_;
+    uint32_t spewNext_;
+    Node* nodes_;
+
+    // This global tracks the nesting level of assemblers, see comments in
+    // spew() in Disassembler-shared.cpp for why this is desirable.
+    //
+    // The variable is atomic to avoid any kind of complaint from thread
+    // sanitizers etc, (it could also be thread-local).  However, trying to look
+    // at disassembly without using --no-threads is basically insane, so you can
+    // ignore the multi-threading implications here.
+    static mozilla::Atomic<uint32_t> live_;
+#endif
+};
+
+}
+}
+
+#endif // jit_shared_Disassembler_shared_h
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -267,16 +267,17 @@ UNIFIED_SOURCES += [
     'jit/Recover.cpp',
     'jit/RegisterAllocator.cpp',
     'jit/RematerializedFrame.cpp',
     'jit/Safepoints.cpp',
     'jit/ScalarReplacement.cpp',
     'jit/shared/Assembler-shared.cpp',
     'jit/shared/BaselineCompiler-shared.cpp',
     'jit/shared/CodeGenerator-shared.cpp',
+    'jit/shared/Disassembler-shared.cpp',
     'jit/shared/Lowering-shared.cpp',
     'jit/SharedIC.cpp',
     'jit/Sink.cpp',
     'jit/Snapshots.cpp',
     'jit/StupidAllocator.cpp',
     'jit/TypedObjectPrediction.cpp',
     'jit/TypePolicy.cpp',
     'jit/ValueNumbering.cpp',
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -1360,40 +1360,46 @@ wasm::GenerateStubs(const ModuleEnvironm
     MacroAssembler masm(MacroAssembler::WasmToken(), alloc);
 
     // Swap in already-allocated empty vectors to avoid malloc/free.
     if (!code->swap(masm))
         return false;
 
     Label throwLabel;
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm import stubs");
+
     for (uint32_t funcIndex = 0; funcIndex < imports.length(); funcIndex++) {
         const FuncImport& fi = imports[funcIndex];
 
         CallableOffsets interpOffsets;
         if (!GenerateImportInterpExit(masm, fi, funcIndex, &throwLabel, &interpOffsets))
             return false;
         if (!code->codeRanges.emplaceBack(CodeRange::ImportInterpExit, funcIndex, interpOffsets))
             return false;
 
         JitExitOffsets jitOffsets;
         if (!GenerateImportJitExit(masm, fi, &throwLabel, &jitOffsets))
             return false;
         if (!code->codeRanges.emplaceBack(funcIndex, jitOffsets))
             return false;
     }
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm export stubs");
+
     for (const FuncExport& fe : exports) {
         Offsets offsets;
         if (!GenerateInterpEntry(masm, fe, &offsets))
             return false;
         if (!code->codeRanges.emplaceBack(CodeRange::InterpEntry, fe.funcIndex(), offsets))
             return false;
     }
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm trap stubs");
+
     for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
         switch (trap) {
           case Trap::Unreachable:
             break;
           // The TODO list of "old" traps to convert to new traps:
           case Trap::IntegerOverflow:
           case Trap::InvalidConversionToInteger:
           case Trap::IntegerDivideByZero:
@@ -1413,16 +1419,18 @@ wasm::GenerateStubs(const ModuleEnvironm
           }
           case Trap::Limit:
             MOZ_CRASH("impossible");
         }
     }
 
     Offsets offsets;
 
+    JitSpew(JitSpew_Codegen, "# Emitting wasm exit stubs");
+
     if (!GenerateOutOfBoundsExit(masm, &throwLabel, &offsets))
         return false;
     if (!code->codeRanges.emplaceBack(CodeRange::OutOfBoundsExit, offsets))
         return false;
 
     if (!GenerateUnalignedExit(masm, &throwLabel, &offsets))
         return false;
     if (!code->codeRanges.emplaceBack(CodeRange::UnalignedExit, offsets))