Backed out 8 changesets (bug 1147403) for debug asserts on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 15 May 2015 15:28:48 -0400
changeset 274898 55bbf00eb10ac5ede635f6560d188932a982758e
parent 274897 49cdc55a2121f9dd5e832d5cd3781c9ad1850308
child 274899 c266af06956b7882e82d08da6e381fe79502be58
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1147403
milestone41.0a1
backs outa1018d31e591e7f0db0ce4838039fad4250949ea
fdb1dcf35e04be74e80524e7bb2b8afccfb599fa
66ab1f78905226ed2a386d22757fa97c7a997f40
8ee01e1488873dd110c613f53083c06a11b44445
615c601284e3a78547c9f200d4473ae39fbcb55d
d7a4b4c31c9472bb6c528b23c36aa61270d71260
b1abceaf0f6eaf5c1c07299a5d080766003728ae
443b1a2a084f48709e1b615f427736ffe2d1dbe2
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
Backed out 8 changesets (bug 1147403) for debug asserts on a CLOSED TREE. Backed out changeset a1018d31e591 (bug 1147403) Backed out changeset fdb1dcf35e04 (bug 1147403) Backed out changeset 66ab1f789052 (bug 1147403) Backed out changeset 8ee01e148887 (bug 1147403) Backed out changeset 615c601284e3 (bug 1147403) Backed out changeset d7a4b4c31c94 (bug 1147403) Backed out changeset b1abceaf0f6e (bug 1147403) Backed out changeset 443b1a2a084f (bug 1147403)
js/src/asmjs/AsmJSValidate.cpp
js/src/builtin/TestingFunctions.cpp
js/src/doc/Debugger/Debugger.md
js/src/doc/Debugger/config.sh
js/src/jit-test/tests/debug/Debugger-onIonCompilation.js
js/src/jit/AliasAnalysis.cpp
js/src/jit/C1Spewer.cpp
js/src/jit/C1Spewer.h
js/src/jit/Ion.cpp
js/src/jit/IonAnalysis.cpp
js/src/jit/IonAnalysis.h
js/src/jit/IonBuilder.h
js/src/jit/JSONSpewer.cpp
js/src/jit/JSONSpewer.h
js/src/jit/JitSpewer.cpp
js/src/jit/JitSpewer.h
js/src/jit/LIR-Common.h
js/src/jit/LIR.cpp
js/src/jit/LIR.h
js/src/jit/Lowering.cpp
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MIRGenerator.h
js/src/jit/MIRGraph.cpp
js/src/jit/MIRGraph.h
js/src/jit/RangeAnalysis.cpp
js/src/jit/RangeAnalysis.h
js/src/jit/Safepoints.cpp
js/src/jit/Snapshots.cpp
js/src/jit/Snapshots.h
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsscript.h
js/src/jsstr.cpp
js/src/jsstr.h
js/src/moz.build
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/HelperThreads.cpp
js/src/vm/Printer.cpp
js/src/vm/Printer.h
--- a/js/src/asmjs/AsmJSValidate.cpp
+++ b/js/src/asmjs/AsmJSValidate.cpp
@@ -7687,18 +7687,16 @@ CheckFunction(ModuleCompiler& m, LifoAll
 
     func->define(fn);
     func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
 
     m.parser().release(mark);
 
     *mir = f.extractMIR();
     (*mir)->initMinAsmJSHeapLength(m.minHeapLength());
-    jit::SpewBeginFunction(*mir, nullptr);
-
     *funcOut = func;
     return true;
 }
 
 static bool
 GenerateCode(ModuleCompiler& m, ModuleCompiler::Func& func, MIRGenerator& mir, LIRGraph& lir)
 {
     int64_t before = PRMJ_Now();
@@ -7765,29 +7763,32 @@ CheckFunctionsSequential(ModuleCompiler&
 
         // In the case of the change-heap function, no MIR is produced.
         if (!mir)
             continue;
 
         int64_t before = PRMJ_Now();
 
         JitContext jcx(m.cx(), &mir->alloc());
-        jit::AutoSpewEndFunction spewEndFunction(mir);
+
+        IonSpewNewFunction(&mir->graph(), NullPtr());
 
         if (!OptimizeMIR(mir))
             return m.failOffset(func->srcBegin(), "internal compiler failure (probably out of memory)");
 
         LIRGraph* lir = GenerateLIR(mir);
         if (!lir)
             return m.failOffset(func->srcBegin(), "internal compiler failure (probably out of memory)");
 
         func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
 
         if (!GenerateCode(m, *func, *mir, *lir))
             return false;
+
+        IonSpewEndFunction();
     }
 
     if (!CheckAllFunctionsDefined(m))
         return false;
 
     return true;
 }
 
@@ -8016,17 +8017,17 @@ CheckFunctions(ModuleCompiler& m)
     // pref, etc) or another thread is currently compiling asm.js in parallel,
     // fall back to sequential compilation. (We could lift the latter
     // constraint by hoisting asmJS* state out of HelperThreadState so multiple
     // concurrent asm.js parallel compilations don't race.)
     ParallelCompilationGuard g;
     if (!ParallelCompilationEnabled(m.cx()) || !g.claim())
         return CheckFunctionsSequential(m);
 
-    JitSpew(JitSpew_IonSyncLogs, "Can't log asm.js script. (Compiled on background thread.)");
+    JitSpew(JitSpew_IonLogs, "Can't log asm.js script. (Compiled on background thread.)");
 
     // Saturate all helper threads.
     size_t numParallelJobs = HelperThreadState().maxAsmJSCompilationThreads();
 
     // Allocate scoped AsmJSParallelTask objects. Each contains a unique
     // LifoAlloc that provides all necessary memory for compilation.
     Vector<AsmJSParallelTask, 0> tasks(m.cx());
     if (!tasks.initCapacity(numParallelJobs))
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -25,17 +25,16 @@
 #include "js/StructuredClone.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeTraverse.h"
 #include "js/Vector.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/ProxyObject.h"
 #include "vm/SavedStacks.h"
-#include "vm/Stack.h"
 #include "vm/TraceLogging.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
@@ -1541,36 +1540,28 @@ js::testingFunc_inIon(JSContext* cx, uns
         JSString* error = JS_NewStringCopyZ(cx, "Ion is disabled.");
         if (!error)
             return false;
 
         args.rval().setString(error);
         return true;
     }
 
-    ScriptFrameIter iter(cx);
-    if (iter.isIon()) {
-        // Reset the counter of the IonScript's script.
-        JitFrameIterator jitIter(cx);
-        ++jitIter;
-        jitIter.script()->resetWarmUpResetCounter();
-    } else {
-        // Check if we missed multiple attempts at compiling the innermost script.
-        JSScript* script = cx->currentScript();
-        if (script && script->getWarmUpResetCount() >= 20) {
-            JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
-            if (!error)
-                return false;
-
-            args.rval().setString(error);
-            return true;
-        }
+    JSScript* script = cx->currentScript();
+    if (script && script->getWarmUpResetCount() >= 20) {
+        JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+        if (!error)
+            return false;
+
+        args.rval().setString(error);
+        return true;
     }
 
-    args.rval().setBoolean(iter.isIon());
+    // false when not in ionMonkey
+    args.rval().setBoolean(false);
     return true;
 }
 
 bool
 js::testingFunc_assertFloat32(JSContext* cx, unsigned argc, jsval* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 2) {
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -220,55 +220,16 @@ compartment.
     API observe activity in only those globals that are reachable by the
     API's user, thus imposing capability-based restrictions on a
     `Debugger`'s reach. However, the `onNewGlobalObject` method allows the
     API user to monitor all global object creation that occurs anywhere
     within the JavaScript system (the "JSRuntime", in SpiderMonkey terms),
     thereby escaping the capability-based limits. For this reason,
     `onNewGlobalObject` is only available to privileged code.
 
-<code>onIonCompilation(<i>graph</i>)</code>
-:   A new IonMonkey compilation result is attached to a script instance of
-    the Debuggee, the <i>graph</i> contains the internal intermediate
-    representations of the compiler.
-
-    The value <i>graph</i> is an object composed of the following properties:
-
-    `json`
-    :   String containing a JSON of the intermediate representation used by
-        the compiler. This JSON string content is composed of 2 intermediate
-        representation of the graph, a `mir` (Middle-level IR), and a
-        `lir` (Low-level IR).
-
-        Both have a property `blocks`, which is an array of basic
-        blocks in [SSA form][ssa-form] which are used to construct the
-        control flow graph. All elements of these arrays are objects which
-        have a `number`, and an `instructions` properties.
-
-        The MIR blocks have additional properties such as the
-        `predecessors` and `successors` of each block, which can
-        be used to reconstruct the control flow graph, with the
-        `number` properties of the blocks.
-
-        The `instructions` properties are array of objects which have
-        an `id` and an `opcode`. The `id` corresponds to the
-        [SSA form][ssa-form] identifier (number) of each instruction, and the
-        `opcode` is a string which represents the instruction.
-
-        This JSON string contains even more detailed internal information
-        which remains undocummented, as it is potentially subject to
-        frequent modifications.
-
-    `scripts`
-    :   Array of [`Debugger.Script`][script] instances. For a block at
-        `mir.blocks[i]` or `lir.blocks[i]` in the JSON, `scripts[i]` is the
-        [`Debugger.Script`][script] containing that block's code.
-
-    This method's return value is ignored.
-
 
 
 ## Function Properties of the Debugger Prototype Object
 
 The functions described below may only be called with a `this` value
 referring to a `Debugger` instance; they may not be used as methods of
 other kinds of objects.
 
--- a/js/src/doc/Debugger/config.sh
+++ b/js/src/doc/Debugger/config.sh
@@ -59,9 +59,8 @@ resource 'img-chrome-pref'        enable
 resource 'img-scratchpad-browser' scratchpad-browser-environment.png $RBASE/7229/scratchpad-browser-environment.png
 resource 'img-example-alert'      debugger-alert.png                 $RBASE/7231/debugger-alert.png
 resource 'img-alloc-plot'         alloc-plot-console.png             $RBASE/8461/alloc-plot-console.png
 
 # External links:
 absolute-label 'protocol' https://wiki.mozilla.org/Remote_Debugging_Protocol "Remote Debugging Protocol"
 absolute-label 'saved-frame' https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/SavedFrame "SavedFrame"
 absolute-label 'bernoulli-trial' https://en.wikipedia.org/wiki/Bernoulli_trial "Bernoulli Trial"
-absolute-label 'ssa-form' https://en.wikipedia.org/wiki/Static_single_assignment_form "SSA form"
deleted file mode 100644
--- a/js/src/jit-test/tests/debug/Debugger-onIonCompilation.js
+++ /dev/null
@@ -1,121 +0,0 @@
-
-function test() {
-    // Force Ion compilation with OSR.
-    for (var res = false; !res; res = inIon()) {};
-    if (typeof res == "string")
-        throw "Skipping test: Ion compilation is disabled/prevented.";
-};
-
-
-// Functions used to assert the representation of the graph which is exported in
-// the JSON string argument.
-function assertInstruction(ins) {
-    assertEq(typeof ins.id, "number");
-    assertEq(ins.id | 0, ins.id);
-    assertEq(typeof ins.opcode, "string");
-}
-
-function assertBlock(block) {
-    assertEq(typeof block, "object");
-    assertEq(typeof block.number, "number");
-    assertEq(block.number | 0, block.number);
-    assertEq(typeof block.instructions, "object");
-    for (var ins of block.instructions)
-        assertInstruction(ins);
-}
-
-function assertGraph(graph, scripts) {
-    assertEq(typeof graph, "object");
-    assertEq(typeof graph.blocks, "object");
-    assertEq(graph.blocks.length, scripts.length);
-    for (var block of graph.blocks)
-        assertBlock(block);
-}
-
-function assertJSON(str, scripts) {
-    assertEq(typeof str, "string");
-
-    var json = JSON.parse(str);
-    assertGraph(json.mir, scripts);
-    assertGraph(json.lir, scripts);
-}
-
-function assertOnIonCompilationArgument(obj) {
-    assertEq(typeof obj, "object");
-    assertEq(typeof obj.scripts, "object");
-    assertJSON(obj.json, obj.scripts);
-}
-
-// Attach the current global to a debugger.
-var hits = 0;
-var g = newGlobal();
-g.parent = this;
-g.eval(`
-  var dbg = new Debugger();
-  var parentw = dbg.addDebuggee(parent);
-  var testw = parentw.makeDebuggeeValue(parent.test);
-  var scriptw = testw.script;
-`);
-
-// Wrap the testing function.
-function check(assert) {
-  // print('reset compilation counter.');
-  with ({}) {       // Prevent Ion compilation.
-    gc();           // Flush previous compilation.
-    hits = 0;       // Synchronized hit counts.
-
-    try {           // Skip this test if we cannot reliably compile with Ion.
-        test();         // Wait until the next Ion compilation.
-    } catch (msg) {
-        if (typeof msg == "string") {
-            // print(msg);
-            return;
-        }
-    }
-
-    assert();       // Run the assertions given as arguments.
-  }
-}
-
-// With the compilation graph inhibited, we should have no output.
-g.eval(`
-  dbg.onIonCompilation = function (graph) {
-    // print('Compiled ' + graph.scripts[0].displayName + ':' + graph.scripts[0].startLine);
-    if (graph.scripts[0] !== scriptw)
-      return;
-    parent.assertOnIonCompilationArgument(graph);
-    parent.hits++;
-  };
-`);
-
-check(function () {
-    // '>= 1' is needed because --ion-eager is too eager.
-    assertEq(hits >= 1, true);
-});
-
-
-// Try re-entering the same compartment as the compiled script.
-g.dbg.onIonCompilation = function (graph) {
-  // print('Compiled ' + graph.scripts[0].displayName + ':' + graph.scripts[0].startLine);
-  if (graph.scripts[0] !== g.scriptw)
-    return;
-  assertOnIonCompilationArgument(graph);
-  hits++;
-};
-check(function () { assertEq(hits >= 1, true); });
-
-// Disable the debugger, and redo the last 2 tests.
-g.eval(`
-  dbg.enabled = false;
-  dbg.onIonCompilation = function (graph) {
-    parent.hits++;
-  };
-`);
-check(function () { assertEq(hits, 0); });
-
-
-g.dbg.enabled = false;
-g.dbg.onIonCompilation = function (graph) {
-  hits++;
-};
-check(function () { assertEq(hits, 0); });
--- a/js/src/jit/AliasAnalysis.cpp
+++ b/js/src/jit/AliasAnalysis.cpp
@@ -9,18 +9,16 @@
 #include <stdio.h>
 
 #include "jit/Ion.h"
 #include "jit/IonBuilder.h"
 #include "jit/JitSpewer.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 
-#include "vm/Printer.h"
-
 using namespace js;
 using namespace js::jit;
 
 using mozilla::Array;
 
 namespace js {
 namespace jit {
 
@@ -125,34 +123,32 @@ BlockMightReach(MBasicBlock* src, MBasic
 }
 
 static void
 IonSpewDependency(MInstruction* load, MInstruction* store, const char* verb, const char* reason)
 {
     if (!JitSpewEnabled(JitSpew_Alias))
         return;
 
-    Fprinter& out = JitSpewPrinter();
-    out.printf("Load ");
-    load->printName(out);
-    out.printf(" %s on store ", verb);
-    store->printName(out);
-    out.printf(" (%s)\n", reason);
+    fprintf(JitSpewFile, "Load ");
+    load->printName(JitSpewFile);
+    fprintf(JitSpewFile, " %s on store ", verb);
+    store->printName(JitSpewFile);
+    fprintf(JitSpewFile, " (%s)\n", reason);
 }
 
 static void
 IonSpewAliasInfo(const char* pre, MInstruction* ins, const char* post)
 {
     if (!JitSpewEnabled(JitSpew_Alias))
         return;
 
-    Fprinter& out = JitSpewPrinter();
-    out.printf("%s ", pre);
-    ins->printName(out);
-    out.printf(" %s\n", post);
+    fprintf(JitSpewFile, "%s ", pre);
+    ins->printName(JitSpewFile);
+    fprintf(JitSpewFile, " %s\n", post);
 }
 
 // This pass annotates every load instruction with the last store instruction
 // on which it depends. The algorithm is optimistic in that it ignores explicit
 // dependencies and only considers loads and stores.
 //
 // Loads inside loops only have an implicit dependency on a store before the
 // loop header if no instruction inside the loop body aliases it. To calculate
@@ -207,20 +203,19 @@ AliasAnalysis::analyze()
 
             if (set.isStore()) {
                 for (AliasSetIterator iter(set); iter; iter++) {
                     if (!stores[*iter].append(*def))
                         return false;
                 }
 
                 if (JitSpewEnabled(JitSpew_Alias)) {
-                    Fprinter& out = JitSpewPrinter();
-                    out.printf("Processing store ");
-                    def->printName(out);
-                    out.printf(" (flags %x)\n", set.flags());
+                    fprintf(JitSpewFile, "Processing store ");
+                    def->printName(JitSpewFile);
+                    fprintf(JitSpewFile, " (flags %x)\n", set.flags());
                 }
             } else {
                 // Find the most recent store on which this instruction depends.
                 MInstruction* lastStore = firstIns;
 
                 for (AliasSetIterator iter(set); iter; iter++) {
                     MInstructionVector& aliasedStores = stores[*iter];
                     for (int i = aliasedStores.length() - 1; i >= 0; i--) {
--- a/js/src/jit/C1Spewer.cpp
+++ b/js/src/jit/C1Spewer.cpp
@@ -11,190 +11,213 @@
 #include "mozilla/SizePrintfMacros.h"
 
 #include <time.h>
 
 #include "jit/BacktrackingAllocator.h"
 #include "jit/LIR.h"
 #include "jit/MIRGraph.h"
 
-#include "vm/Printer.h"
-
 using namespace js;
 using namespace js::jit;
 
+bool
+C1Spewer::init(const char* path)
+{
+    spewout_ = fopen(path, "w");
+    return spewout_ != nullptr;
+}
+
 void
-C1Spewer::beginFunction(MIRGraph* graph, JSScript* script)
+C1Spewer::beginFunction(MIRGraph* graph, HandleScript script)
 {
+    if (!spewout_)
+        return;
+
     this->graph  = graph;
 
-    out_.printf("begin_compilation\n");
+    fprintf(spewout_, "begin_compilation\n");
     if (script) {
-        out_.printf("  name \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno());
-        out_.printf("  method \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno());
+        fprintf(spewout_, "  name \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno());
+        fprintf(spewout_, "  method \"%s:%" PRIuSIZE "\"\n", script->filename(), script->lineno());
     } else {
-        out_.printf("  name \"asm.js compilation\"\n");
-        out_.printf("  method \"asm.js compilation\"\n");
+        fprintf(spewout_, "  name \"asm.js compilation\"\n");
+        fprintf(spewout_, "  method \"asm.js compilation\"\n");
     }
-    out_.printf("  date %d\n", (int)time(nullptr));
-    out_.printf("end_compilation\n");
+    fprintf(spewout_, "  date %d\n", (int)time(nullptr));
+    fprintf(spewout_, "end_compilation\n");
 }
 
 void
 C1Spewer::spewPass(const char* pass)
 {
-    out_.printf("begin_cfg\n");
-    out_.printf("  name \"%s\"\n", pass);
+    if (!spewout_)
+        return;
+
+    fprintf(spewout_, "begin_cfg\n");
+    fprintf(spewout_, "  name \"%s\"\n", pass);
 
     for (MBasicBlockIterator block(graph->begin()); block != graph->end(); block++)
-        spewPass(out_, *block);
+        spewPass(spewout_, *block);
 
-    out_.printf("end_cfg\n");
+    fprintf(spewout_, "end_cfg\n");
+    fflush(spewout_);
 }
 
 void
 C1Spewer::spewIntervals(const char* pass, BacktrackingAllocator* regalloc)
 {
-    out_.printf("begin_intervals\n");
-    out_.printf(" name \"%s\"\n", pass);
+    if (!spewout_)
+        return;
+
+    fprintf(spewout_, "begin_intervals\n");
+    fprintf(spewout_, " name \"%s\"\n", pass);
 
     size_t nextId = 0x4000;
     for (MBasicBlockIterator block(graph->begin()); block != graph->end(); block++)
-        spewIntervals(out_, *block, regalloc, nextId);
+        spewIntervals(spewout_, *block, regalloc, nextId);
 
-    out_.printf("end_intervals\n");
+    fprintf(spewout_, "end_intervals\n");
+    fflush(spewout_);
 }
 
 void
 C1Spewer::endFunction()
 {
 }
 
-static void
-DumpDefinition(GenericPrinter& out, MDefinition* def)
+void
+C1Spewer::finish()
 {
-    out.printf("      ");
-    out.printf("%u %u ", def->id(), unsigned(def->useCount()));
-    def->printName(out);
-    out.printf(" ");
-    def->printOpcode(out);
-    out.printf(" <|@\n");
+    if (spewout_)
+        fclose(spewout_);
 }
 
 static void
-DumpLIR(GenericPrinter& out, LNode* ins)
+DumpDefinition(FILE* fp, MDefinition* def)
 {
-    out.printf("      ");
-    out.printf("%d ", ins->id());
-    ins->dump(out);
-    out.printf(" <|@\n");
+    fprintf(fp, "      ");
+    fprintf(fp, "%u %u ", def->id(), unsigned(def->useCount()));
+    def->printName(fp);
+    fprintf(fp, " ");
+    def->printOpcode(fp);
+    fprintf(fp, " <|@\n");
+}
+
+static void
+DumpLIR(FILE* fp, LNode* ins)
+{
+    fprintf(fp, "      ");
+    fprintf(fp, "%d ", ins->id());
+    ins->dump(fp);
+    fprintf(fp, " <|@\n");
 }
 
 void
-C1Spewer::spewIntervals(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId)
+C1Spewer::spewIntervals(FILE* fp, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId)
 {
     for (size_t k = 0; k < ins->numDefs(); k++) {
         uint32_t id = ins->getDef(k)->virtualRegister();
         VirtualRegister* vreg = &regalloc->vregs[id];
 
         for (size_t i = 0; i < vreg->numIntervals(); i++) {
             LiveInterval* live = vreg->getInterval(i);
             if (live->numRanges()) {
-                out.printf("%d object \"", (i == 0) ? id : int32_t(nextId++));
-                out.printf("%s", live->getAllocation()->toString());
-                out.printf("\" %d -1", id);
+                fprintf(fp, "%d object \"", (i == 0) ? id : int32_t(nextId++));
+                fprintf(fp, "%s", live->getAllocation()->toString());
+                fprintf(fp, "\" %d -1", id);
                 for (size_t j = 0; j < live->numRanges(); j++) {
-                    out.printf(" [%u, %u[", live->getRange(j)->from.bits(),
-                               live->getRange(j)->to.bits());
+                    fprintf(fp, " [%u, %u[", live->getRange(j)->from.bits(),
+                            live->getRange(j)->to.bits());
                 }
                 for (UsePositionIterator usePos(live->usesBegin()); usePos != live->usesEnd(); usePos++)
-                    out.printf(" %u M", usePos->pos.bits());
-                out.printf(" \"\"\n");
+                    fprintf(fp, " %u M", usePos->pos.bits());
+                fprintf(fp, " \"\"\n");
             }
         }
     }
 }
 
 void
-C1Spewer::spewIntervals(GenericPrinter& out, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId)
+C1Spewer::spewIntervals(FILE* fp, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId)
 {
     LBlock* lir = block->lir();
     if (!lir)
         return;
 
     for (size_t i = 0; i < lir->numPhis(); i++)
-        spewIntervals(out, regalloc, lir->getPhi(i), nextId);
+        spewIntervals(fp, regalloc, lir->getPhi(i), nextId);
 
     for (LInstructionIterator ins = lir->begin(); ins != lir->end(); ins++)
-        spewIntervals(out, regalloc, *ins, nextId);
+        spewIntervals(fp, regalloc, *ins, nextId);
 }
 
 void
-C1Spewer::spewPass(GenericPrinter& out, MBasicBlock* block)
+C1Spewer::spewPass(FILE* fp, MBasicBlock* block)
 {
-    out.printf("  begin_block\n");
-    out.printf("    name \"B%d\"\n", block->id());
-    out.printf("    from_bci -1\n");
-    out.printf("    to_bci -1\n");
+    fprintf(fp, "  begin_block\n");
+    fprintf(fp, "    name \"B%d\"\n", block->id());
+    fprintf(fp, "    from_bci -1\n");
+    fprintf(fp, "    to_bci -1\n");
 
-    out.printf("    predecessors");
+    fprintf(fp, "    predecessors");
     for (uint32_t i = 0; i < block->numPredecessors(); i++) {
         MBasicBlock* pred = block->getPredecessor(i);
-        out.printf(" \"B%d\"", pred->id());
+        fprintf(fp, " \"B%d\"", pred->id());
     }
-    out.printf("\n");
+    fprintf(fp, "\n");
 
-    out.printf("    successors");
+    fprintf(fp, "    successors");
     for (uint32_t i = 0; i < block->numSuccessors(); i++) {
         MBasicBlock* successor = block->getSuccessor(i);
-        out.printf(" \"B%d\"", successor->id());
+        fprintf(fp, " \"B%d\"", successor->id());
     }
-    out.printf("\n");
+    fprintf(fp, "\n");
 
-    out.printf("    xhandlers\n");
-    out.printf("    flags\n");
+    fprintf(fp, "    xhandlers\n");
+    fprintf(fp, "    flags\n");
 
     if (block->lir() && block->lir()->begin() != block->lir()->end()) {
-        out.printf("    first_lir_id %d\n", block->lir()->firstId());
-        out.printf("    last_lir_id %d\n", block->lir()->lastId());
+        fprintf(fp, "    first_lir_id %d\n", block->lir()->firstId());
+        fprintf(fp, "    last_lir_id %d\n", block->lir()->lastId());
     }
 
-    out.printf("    begin_states\n");
+    fprintf(fp, "    begin_states\n");
 
     if (block->entryResumePoint()) {
-        out.printf("      begin_locals\n");
-        out.printf("        size %d\n", (int)block->numEntrySlots());
-        out.printf("        method \"None\"\n");
+        fprintf(fp, "      begin_locals\n");
+        fprintf(fp, "        size %d\n", (int)block->numEntrySlots());
+        fprintf(fp, "        method \"None\"\n");
         for (uint32_t i = 0; i < block->numEntrySlots(); i++) {
             MDefinition* ins = block->getEntrySlot(i);
-            out.printf("        ");
-            out.printf("%d ", i);
+            fprintf(fp, "        ");
+            fprintf(fp, "%d ", i);
             if (ins->isUnused())
-                out.printf("unused");
+                fprintf(fp, "unused");
             else
-                ins->printName(out);
-            out.printf("\n");
+                ins->printName(fp);
+            fprintf(fp, "\n");
         }
-        out.printf("      end_locals\n");
+        fprintf(fp, "      end_locals\n");
     }
-    out.printf("    end_states\n");
+    fprintf(fp, "    end_states\n");
 
-    out.printf("    begin_HIR\n");
+    fprintf(fp, "    begin_HIR\n");
     for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++)
-        DumpDefinition(out, *phi);
+        DumpDefinition(fp, *phi);
     for (MInstructionIterator i(block->begin()); i != block->end(); i++)
-        DumpDefinition(out, *i);
-    out.printf("    end_HIR\n");
+        DumpDefinition(fp, *i);
+    fprintf(fp, "    end_HIR\n");
 
     if (block->lir()) {
-        out.printf("    begin_LIR\n");
+        fprintf(fp, "    begin_LIR\n");
         for (size_t i = 0; i < block->lir()->numPhis(); i++)
-            DumpLIR(out, block->lir()->getPhi(i));
+            DumpLIR(fp, block->lir()->getPhi(i));
         for (LInstructionIterator i(block->lir()->begin()); i != block->lir()->end(); i++)
-            DumpLIR(out, *i);
-        out.printf("    end_LIR\n");
+            DumpLIR(fp, *i);
+        fprintf(fp, "    end_LIR\n");
     }
 
-    out.printf("  end_block\n");
+    fprintf(fp, "  end_block\n");
 }
 
 #endif /* DEBUG */
 
--- a/js/src/jit/C1Spewer.h
+++ b/js/src/jit/C1Spewer.h
@@ -7,45 +7,46 @@
 #ifndef jit_C1Spewer_h
 #define jit_C1Spewer_h
 
 #ifdef DEBUG
 
 #include "NamespaceImports.h"
 
 #include "js/RootingAPI.h"
-#include "vm/Printer.h"
 
 namespace js {
 namespace jit {
 
 class BacktrackingAllocator;
 class MBasicBlock;
 class MIRGraph;
 class LNode;
 
 class C1Spewer
 {
     MIRGraph* graph;
-    GenericPrinter& out_;
+    FILE* spewout_;
 
   public:
-    explicit C1Spewer(GenericPrinter& out)
-      : graph(nullptr), out_(out)
+    C1Spewer()
+      : graph(nullptr), spewout_(nullptr)
     { }
 
-    void beginFunction(MIRGraph* graph, JSScript* script);
+    bool init(const char* path);
+    void beginFunction(MIRGraph* graph, HandleScript script);
     void spewPass(const char* pass);
     void spewIntervals(const char* pass, BacktrackingAllocator* regalloc);
     void endFunction();
+    void finish();
 
   private:
-    void spewPass(GenericPrinter& out, MBasicBlock* block);
-    void spewIntervals(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId);
-    void spewIntervals(GenericPrinter& out, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId);
+    void spewPass(FILE* fp, MBasicBlock* block);
+    void spewIntervals(FILE* fp, BacktrackingAllocator* regalloc, LNode* ins, size_t& nextId);
+    void spewIntervals(FILE* fp, MBasicBlock* block, BacktrackingAllocator* regalloc, size_t& nextId);
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* DEBUG */
 
 #endif /* jit_C1Spewer_h */
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -36,23 +36,21 @@
 #include "jit/LoopUnroller.h"
 #include "jit/Lowering.h"
 #include "jit/PerfSpewer.h"
 #include "jit/RangeAnalysis.h"
 #include "jit/ScalarReplacement.h"
 #include "jit/Sink.h"
 #include "jit/StupidAllocator.h"
 #include "jit/ValueNumbering.h"
-#include "vm/Debugger.h"
 #include "vm/HelperThreads.h"
 #include "vm/TraceLogging.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
-#include "vm/Debugger-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::ThreadLocal;
 
 // Assert that JitCode is gc::Cell aligned.
 JS_STATIC_ASSERT(sizeof(JitCode) % gc::CellSize == 0);
@@ -389,59 +387,16 @@ JitCompartment::ensureIonStubsExist(JSCo
         stringConcatStub_ = generateStringConcatStub(cx);
         if (!stringConcatStub_)
             return false;
     }
 
     return true;
 }
 
-// This function initializes the values which are given to the Debugger
-// onIonCompilation hook, if the compilation was successful, and if Ion
-// compilations of this compartment are watched by any debugger.
-//
-// This function must be called in the same AutoEnterAnalysis section as the
-// CodeGenerator::link. Failing to do so might leave room to interleave other
-// allocations which can invalidate any JSObject / JSFunction referenced by the
-// MIRGraph.
-//
-// This function ignores any allocation failure and returns whether the
-// Debugger::onIonCompilation should be called.
-static inline bool
-PrepareForDebuggerOnIonCompilationHook(JSContext* cx, bool success, jit::MIRGraph& graph,
-                                       AutoScriptVector* scripts, LSprinter* spew)
-{
-    if (!success)
-        return false;
-
-    if (!Debugger::observesIonCompilation(cx))
-        return false;
-
-    // fireOnIonCompilation failures are ignored, do the same here.
-    if (!scripts->reserve(graph.numBlocks())) {
-        cx->clearPendingException();
-        return false;
-    }
-
-    // Collect the list of scripts which are inlined in the MIRGraph.
-    for (jit::MBasicBlockIterator block(graph.begin()); block != graph.end(); block++)
-        scripts->infallibleAppend(block->info().script());
-
-    // Spew the JSON graph made for the Debugger at the end of the LifoAlloc
-    // used by the compiler. This would not prevent unexpected GC from the
-    // compartment of the Debuggee, but do them as part of the compartment of
-    // the Debugger when the content is copied over to a JSString.
-    jit::JSONSpewer spewer(*spew);
-    spewer.spewDebuggerGraph(&graph);
-    if (spew->hadOutOfMemory())
-        return false;
-
-    return true;
-}
-
 void
 jit::FinishOffThreadBuilder(JSContext* cx, IonBuilder* builder)
 {
     // Clean the references to the pending IonBuilder, if we just finished it.
     if (builder->script()->hasIonScript() && builder->script()->pendingIonBuilder() == builder)
         builder->script()->setPendingIonBuilder(cx, nullptr);
     if (builder->isInList())
         builder->remove();
@@ -512,57 +467,43 @@ jit::LazyLinkTopActivation(JSContext* cx
     JitFrameIterator it(iter);
     LazyLinkExitFrameLayout* ll = it.exitFrame()->as<LazyLinkExitFrameLayout>();
     JSScript* calleeScript = ScriptFromCalleeToken(ll->jsFrame()->calleeToken());
 
     // Get the pending builder from the Ion frame.
     IonBuilder* builder = calleeScript->ionScript()->pendingBuilder();
     calleeScript->setPendingIonBuilder(cx, nullptr);
 
+    AutoEnterAnalysis enterTypes(cx);
     RootedScript script(cx, builder->script());
 
-    // See PrepareForDebuggerOnIonCompilationHook
-    bool callOnIonCompilation = false;
-    AutoScriptVector debugScripts(cx);
-    LSprinter debugPrinter(builder->alloc().lifoAlloc());
-
     // Remove from pending.
     builder->remove();
 
-    CodeGenerator* codegen = builder->backgroundCodegen();
-    bool success = false;
-    if (codegen) {
-        JitContext jctx(cx, &builder->alloc());
-        AutoEnterAnalysis enterTypes(cx);
+    if (CodeGenerator* codegen = builder->backgroundCodegen()) {
         js::TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
         TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
         AutoTraceLog logScript(logger, event);
         AutoTraceLog logLink(logger, TraceLogger_IonLinking);
 
+        JitContext jctx(cx, &builder->alloc());
+
         // Root the assembler until the builder is finished below. As it
         // was constructed off thread, the assembler has not been rooted
         // previously, though any GC activity would discard the builder.
         codegen->masm.constructRoot(cx);
 
-        success = codegen->link(cx, builder->constraints());
-        if (!success) {
+        if (!codegen->link(cx, builder->constraints())) {
             // Silently ignore OOM during code generation. The assembly code
             // doesn't has code to handle it after linking happened. So it's
             // not OK to throw a catchable exception from there.
             cx->clearPendingException();
         }
-
-        callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
-            cx, success, builder->graph(), &debugScripts, &debugPrinter);
     }
 
-    // Without AutoEnterAnalysis scope.
-    if (callOnIonCompilation)
-        Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
-
     FinishOffThreadBuilder(cx, builder);
 
     MOZ_ASSERT(script->hasBaselineScript());
     MOZ_ASSERT(script->baselineOrIonRawPointer());
 
     return script->baselineOrIonRawPointer();
 }
 
@@ -1227,60 +1168,59 @@ jit::ToggleBarriers(JS::Zone* zone, bool
 
 namespace js {
 namespace jit {
 
 bool
 OptimizeMIR(MIRGenerator* mir)
 {
     MIRGraph& graph = mir->graph();
-    GraphSpewer& gs = mir->graphSpewer();
     TraceLoggerThread* logger;
     if (GetJitContext()->runtime->onMainThread())
         logger = TraceLoggerForMainThread(GetJitContext()->runtime);
     else
         logger = TraceLoggerForCurrentThread();
 
     if (!mir->compilingAsmJS()) {
         if (!MakeMRegExpHoistable(graph))
             return false;
     }
 
-    gs.spewPass("BuildSSA");
+    IonSpewPass("BuildSSA");
     AssertBasicGraphCoherency(graph);
 
     if (mir->shouldCancel("Start"))
         return false;
 
     if (!mir->compilingAsmJS()) {
         AutoTraceLog log(logger, TraceLogger_FoldTests);
         FoldTests(graph);
-        gs.spewPass("Fold Tests");
+        IonSpewPass("Fold Tests");
         AssertBasicGraphCoherency(graph);
 
         if (mir->shouldCancel("Fold Tests"))
             return false;
     }
 
     {
         AutoTraceLog log(logger, TraceLogger_SplitCriticalEdges);
         if (!SplitCriticalEdges(graph))
             return false;
-        gs.spewPass("Split Critical Edges");
+        IonSpewPass("Split Critical Edges");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Split Critical Edges"))
             return false;
     }
 
     {
         AutoTraceLog log(logger, TraceLogger_RenumberBlocks);
         if (!RenumberBlocks(graph))
             return false;
-        gs.spewPass("Renumber Blocks");
+        IonSpewPass("Renumber Blocks");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Renumber Blocks"))
             return false;
     }
 
     {
         AutoTraceLog log(logger, TraceLogger_DominatorTree);
@@ -1298,17 +1238,17 @@ OptimizeMIR(MIRGenerator* mir)
         // script contains a try-statement, we only compiled the try block and not
         // the catch or finally blocks, so in this case it's also invalid to use
         // aggressive phi elimination.
         Observability observability = graph.hasTryBlock()
                                       ? ConservativeObservability
                                       : AggressiveObservability;
         if (!EliminatePhis(mir, graph, observability))
             return false;
-        gs.spewPass("Eliminate phis");
+        IonSpewPass("Eliminate phis");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Eliminate phis"))
             return false;
 
         if (!BuildPhiReverseMapping(graph))
             return false;
         AssertExtendedGraphCoherency(graph);
@@ -1317,51 +1257,51 @@ OptimizeMIR(MIRGenerator* mir)
         if (mir->shouldCancel("Phi reverse mapping"))
             return false;
     }
 
     if (mir->optimizationInfo().scalarReplacementEnabled()) {
         AutoTraceLog log(logger, TraceLogger_ScalarReplacement);
         if (!ScalarReplacement(mir, graph))
             return false;
-        gs.spewPass("Scalar Replacement");
+        IonSpewPass("Scalar Replacement");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Scalar Replacement"))
             return false;
     }
 
     if (!mir->compilingAsmJS()) {
         AutoTraceLog log(logger, TraceLogger_ApplyTypes);
         if (!ApplyTypeInformation(mir, graph))
             return false;
-        gs.spewPass("Apply types");
+        IonSpewPass("Apply types");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Apply types"))
             return false;
     }
 
     if (mir->optimizationInfo().eagerSimdUnboxEnabled()) {
         AutoTraceLog log(logger, TraceLogger_EagerSimdUnbox);
         if (!EagerSimdUnbox(mir, graph))
             return false;
-        gs.spewPass("Eager Simd Unbox");
+        IonSpewPass("Eager Simd Unbox");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Eager Simd Unbox"))
             return false;
     }
 
     if (mir->optimizationInfo().amaEnabled()) {
         AutoTraceLog log(logger, TraceLogger_AlignmentMaskAnalysis);
         AlignmentMaskAnalysis ama(graph);
         if (!ama.analyze())
             return false;
-        gs.spewPass("Alignment Mask Analysis");
+        IonSpewPass("Alignment Mask Analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Alignment Mask Analysis"))
             return false;
     }
 
     ValueNumberer gvn(mir, graph);
     if (!gvn.init())
@@ -1371,17 +1311,17 @@ OptimizeMIR(MIRGenerator* mir)
     // loads across stores.
     if (mir->optimizationInfo().licmEnabled() ||
         mir->optimizationInfo().gvnEnabled())
     {
         AutoTraceLog log(logger, TraceLogger_AliasAnalysis);
         AliasAnalysis analysis(mir, graph);
         if (!analysis.analyze())
             return false;
-        gs.spewPass("Alias analysis");
+        IonSpewPass("Alias analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Alias analysis"))
             return false;
 
         if (!mir->compilingAsmJS()) {
             // Eliminating dead resume point operands requires basic block
             // instructions to be numbered. Reuse the numbering computed during
@@ -1393,208 +1333,207 @@ OptimizeMIR(MIRGenerator* mir)
                 return false;
         }
     }
 
     if (mir->optimizationInfo().gvnEnabled()) {
         AutoTraceLog log(logger, TraceLogger_GVN);
         if (!gvn.run(ValueNumberer::UpdateAliasAnalysis))
             return false;
-        gs.spewPass("GVN");
+        IonSpewPass("GVN");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("GVN"))
             return false;
     }
 
     if (mir->optimizationInfo().licmEnabled()) {
         AutoTraceLog log(logger, TraceLogger_LICM);
         // LICM can hoist instructions from conditional branches and trigger
         // repeated bailouts. Disable it if this script is known to bailout
         // frequently.
         JSScript* script = mir->info().script();
         if (!script || !script->hadFrequentBailouts()) {
             if (!LICM(mir, graph))
                 return false;
-            gs.spewPass("LICM");
+            IonSpewPass("LICM");
             AssertExtendedGraphCoherency(graph);
 
             if (mir->shouldCancel("LICM"))
                 return false;
         }
     }
 
     if (mir->optimizationInfo().rangeAnalysisEnabled()) {
         AutoTraceLog log(logger, TraceLogger_RangeAnalysis);
         RangeAnalysis r(mir, graph);
         if (!r.addBetaNodes())
             return false;
-        gs.spewPass("Beta");
+        IonSpewPass("Beta");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("RA Beta"))
             return false;
 
         if (!r.analyze() || !r.addRangeAssertions())
             return false;
-        gs.spewPass("Range Analysis");
+        IonSpewPass("Range Analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Range Analysis"))
             return false;
 
         if (!r.removeBetaNodes())
             return false;
-        gs.spewPass("De-Beta");
+        IonSpewPass("De-Beta");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("RA De-Beta"))
             return false;
 
         if (mir->optimizationInfo().gvnEnabled()) {
             bool shouldRunUCE = false;
             if (!r.prepareForUCE(&shouldRunUCE))
                 return false;
-            gs.spewPass("RA check UCE");
+            IonSpewPass("RA check UCE");
             AssertExtendedGraphCoherency(graph);
 
             if (mir->shouldCancel("RA check UCE"))
                 return false;
 
             if (shouldRunUCE) {
                 if (!gvn.run(ValueNumberer::DontUpdateAliasAnalysis))
                     return false;
-                gs.spewPass("UCE After RA");
+                IonSpewPass("UCE After RA");
                 AssertExtendedGraphCoherency(graph);
 
                 if (mir->shouldCancel("UCE After RA"))
                     return false;
             }
         }
 
         if (mir->optimizationInfo().autoTruncateEnabled()) {
             if (!r.truncate())
                 return false;
-            gs.spewPass("Truncate Doubles");
+            IonSpewPass("Truncate Doubles");
             AssertExtendedGraphCoherency(graph);
 
             if (mir->shouldCancel("Truncate Doubles"))
                 return false;
         }
 
         if (mir->optimizationInfo().loopUnrollingEnabled()) {
             AutoTraceLog log(logger, TraceLogger_LoopUnrolling);
 
             if (!UnrollLoops(graph, r.loopIterationBounds))
                 return false;
 
-            gs.spewPass("Unroll Loops");
+            IonSpewPass("Unroll Loops");
             AssertExtendedGraphCoherency(graph);
         }
     }
 
     if (mir->optimizationInfo().eaaEnabled()) {
         AutoTraceLog log(logger, TraceLogger_EffectiveAddressAnalysis);
         EffectiveAddressAnalysis eaa(mir, graph);
         if (!eaa.analyze())
             return false;
-        gs.spewPass("Effective Address Analysis");
+        IonSpewPass("Effective Address Analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Effective Address Analysis"))
             return false;
     }
 
     {
         AutoTraceLog log(logger, TraceLogger_EliminateDeadCode);
         if (!EliminateDeadCode(mir, graph))
             return false;
-        gs.spewPass("DCE");
+        IonSpewPass("DCE");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("DCE"))
             return false;
     }
 
     {
         AutoTraceLog log(logger, TraceLogger_EliminateDeadCode);
         if (!Sink(mir, graph))
             return false;
-        gs.spewPass("Sink");
+        IonSpewPass("Sink");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Sink"))
             return false;
     }
 
     // Make loops contiguous. We do this after GVN/UCE and range analysis,
     // which can remove CFG edges, exposing more blocks that can be moved.
     {
         AutoTraceLog log(logger, TraceLogger_MakeLoopsContiguous);
         if (!MakeLoopsContiguous(graph))
             return false;
-        gs.spewPass("Make loops contiguous");
+        IonSpewPass("Make loops contiguous");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Make loops contiguous"))
             return false;
     }
 
     // Passes after this point must not move instructions; these analyses
     // depend on knowing the final order in which instructions will execute.
 
     if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) {
         AutoTraceLog log(logger, TraceLogger_EdgeCaseAnalysis);
         EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
         if (!edgeCaseAnalysis.analyzeLate())
             return false;
-        gs.spewPass("Edge Case Analysis (Late)");
+        IonSpewPass("Edge Case Analysis (Late)");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Edge Case Analysis (Late)"))
             return false;
     }
 
     if (mir->optimizationInfo().eliminateRedundantChecksEnabled()) {
         AutoTraceLog log(logger, TraceLogger_EliminateRedundantChecks);
         // Note: check elimination has to run after all other passes that move
         // instructions. Since check uses are replaced with the actual index,
         // code motion after this pass could incorrectly move a load or store
         // before its bounds check.
         if (!EliminateRedundantChecks(graph))
             return false;
-        gs.spewPass("Bounds Check Elimination");
+        IonSpewPass("Bounds Check Elimination");
         AssertGraphCoherency(graph);
     }
 
     return true;
 }
 
 LIRGraph*
 GenerateLIR(MIRGenerator* mir)
 {
     MIRGraph& graph = mir->graph();
-    GraphSpewer& gs = mir->graphSpewer();
 
     TraceLoggerThread* logger;
     if (GetJitContext()->runtime->onMainThread())
         logger = TraceLoggerForMainThread(GetJitContext()->runtime);
     else
         logger = TraceLoggerForCurrentThread();
 
     LIRGraph* lir = mir->alloc().lifoAlloc()->new_<LIRGraph>(&graph);
     if (!lir || !lir->init())
         return nullptr;
 
     LIRGenerator lirgen(mir, graph, *lir);
     {
         AutoTraceLog log(logger, TraceLogger_GenerateLIR);
         if (!lirgen.generate())
             return nullptr;
-        gs.spewPass("Generate LIR");
+        IonSpewPass("Generate LIR");
 
         if (mir->shouldCancel("Generate LIR"))
             return nullptr;
     }
 
     AllocationIntegrityState integrity(*lir);
 
     {
@@ -1611,32 +1550,32 @@ GenerateLIR(MIRGenerator* mir)
             if (!regalloc.go())
                 return nullptr;
 
 #ifdef DEBUG
             if (!integrity.check(false))
                 return nullptr;
 #endif
 
-            gs.spewPass("Allocate Registers [Backtracking]");
+            IonSpewPass("Allocate Registers [Backtracking]");
             break;
           }
 
           case RegisterAllocator_Stupid: {
             // Use the integrity checker to populate safepoint information, so
             // run it in all builds.
             if (!integrity.record())
                 return nullptr;
 
             StupidAllocator regalloc(mir, &lirgen, *lir);
             if (!regalloc.go())
                 return nullptr;
             if (!integrity.check(true))
                 return nullptr;
-            gs.spewPass("Allocate Registers [Stupid]");
+            IonSpewPass("Allocate Registers [Stupid]");
             break;
           }
 
           default:
             MOZ_CRASH("Bad regalloc");
         }
 
         if (mir->shouldCancel("Allocate Registers"))
@@ -1668,17 +1607,16 @@ GenerateCode(MIRGenerator* mir, LIRGraph
     return codegen;
 }
 
 CodeGenerator*
 CompileBackEnd(MIRGenerator* mir)
 {
     // Everything in CompileBackEnd can potentially run on a helper thread.
     AutoEnterIonCompilation enter;
-    AutoSpewEndFunction spewEndFunction(mir);
 
     if (!OptimizeMIR(mir))
         return nullptr;
 
     LIRGraph* lir = GenerateLIR(mir);
     if (!lir)
         return nullptr;
 
@@ -1687,16 +1625,17 @@ CompileBackEnd(MIRGenerator* mir)
 
 void
 AttachFinishedCompilations(JSContext* cx)
 {
     JitCompartment* ion = cx->compartment()->jitCompartment();
     if (!ion)
         return;
 
+    AutoEnterAnalysis enterTypes(cx);
     AutoLockHelperThreadState lock;
 
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
 
     TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
 
     // Incorporate any off thread compilations for the compartment which have
     // finished, failed or have been cancelled.
@@ -1738,57 +1677,41 @@ AttachFinishedCompilations(JSContext* cx
 
             if (onStack) {
                 builder->script()->setPendingIonBuilder(cx, builder);
                 HelperThreadState().ionLazyLinkList().insertFront(builder);
                 continue;
             }
         }
 
-        // See PrepareForDebuggerOnIonCompilationHook
-        bool callOnIonCompilation = false;
-        AutoScriptVector debugScripts(cx);
-        LSprinter debugPrinter(builder->alloc().lifoAlloc());
-
-        RootedScript script(cx, builder->script());
-        CodeGenerator* codegen = builder->backgroundCodegen();
-        bool success = false;
-        if (codegen) {
+        if (CodeGenerator* codegen = builder->backgroundCodegen()) {
+            RootedScript script(cx, builder->script());
             JitContext jctx(cx, &builder->alloc());
-            AutoEnterAnalysis enterTypes(cx);
             TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script);
             AutoTraceLog logScript(logger, event);
             AutoTraceLog logLink(logger, TraceLogger_IonLinking);
 
             // Root the assembler until the builder is finished below. As it
             // was constructed off thread, the assembler has not been rooted
             // previously, though any GC activity would discard the builder.
             codegen->masm.constructRoot(cx);
 
+            bool success;
             {
                 AutoUnlockHelperThreadState unlock;
                 success = codegen->link(cx, builder->constraints());
             }
 
             if (!success) {
                 // Silently ignore OOM during code generation. The caller is
                 // InvokeInterruptCallback, which always runs at a
                 // nondeterministic time. It's not OK to throw a catchable
                 // exception from there.
                 cx->clearPendingException();
             }
-
-            callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
-                cx, success, builder->graph(), &debugScripts, &debugPrinter);
-        }
-
-        if (callOnIonCompilation) {
-            // Without AutoEnterAnalysis scope.
-            AutoUnlockHelperThreadState unlock;
-            Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
         }
 
         FinishOffThreadBuilder(cx, builder);
     }
 }
 
 void
 MIRGenerator::traceNurseryObjects(JSTracer* trc)
@@ -1931,16 +1854,18 @@ IonCompile(JSContext* cx, JSScript* scri
     ScopedJSDeletePtr<LifoAlloc> autoDelete(alloc);
 
     TempAllocator* temp = alloc->new_<TempAllocator>(alloc);
     if (!temp)
         return AbortReason_Alloc;
 
     JitContext jctx(cx, temp);
 
+    AutoEnterAnalysis enter(cx);
+
     if (!cx->compartment()->ensureJitCompartmentExists(cx))
         return AbortReason_Alloc;
 
     if (!cx->compartment()->jitCompartment()->ensureIonStubsExist(cx))
         return AbortReason_Alloc;
 
     MIRGraph* graph = alloc->new_<MIRGraph>(temp);
     if (!graph)
@@ -1985,28 +1910,25 @@ IonCompile(JSContext* cx, JSScript* scri
     MOZ_ASSERT(recompile == builder->script()->hasIonScript());
     MOZ_ASSERT(builder->script()->canIonCompile());
 
     RootedScript builderScript(cx, builder->script());
 
     if (recompile)
         builderScript->ionScript()->setRecompiling();
 
-    SpewBeginFunction(builder, builderScript);
-
-    bool succeeded;
-    {
-        AutoEnterAnalysis enter(cx);
-        succeeded = builder->build();
-        builder->clearForBackEnd();
-    }
+#ifdef DEBUG
+    IonSpewFunction ionSpewFunction(graph, builderScript);
+#endif
+
+    bool succeeded = builder->build();
+    builder->clearForBackEnd();
 
     if (!succeeded) {
         AbortReason reason = builder->abortReason();
-        builder->graphSpewer().endFunction();
         if (reason == AbortReason_PreliminaryObjects) {
             // Some group was accessed which has associated preliminary objects
             // to analyze. Do this now and we will try to build again shortly.
             const MIRGenerator::ObjectGroupVector& groups = builder->abortedPreliminaryGroups();
             for (size_t i = 0; i < groups.length(); i++) {
                 ObjectGroup* group = groups[i];
                 if (group->newScript()) {
                     if (!group->newScript()->maybeAnalyze(cx, group, nullptr, /* force = */ true))
@@ -2030,65 +1952,49 @@ IonCompile(JSContext* cx, JSScript* scri
         return reason;
     }
 
     // If possible, compile the script off thread.
     if (options.offThreadCompilationAvailable()) {
         if (!recompile)
             builderScript->setIonScript(cx, ION_COMPILING_SCRIPT);
 
-        JitSpew(JitSpew_IonSyncLogs, "Can't log script %s:%" PRIuSIZE
-                ". (Compiled on background thread.)",
+        JitSpew(JitSpew_IonLogs, "Can't log script %s:%" PRIuSIZE ". (Compiled on background thread.)",
                 builderScript->filename(), builderScript->lineno());
 
         JSRuntime* rt = cx->runtime();
         if (!builder->nurseryObjects().empty() && !rt->jitRuntime()->hasIonNurseryObjects()) {
             // Ensure the builder's nursery objects are marked when a nursery
             // GC happens on the main thread.
             MarkOffThreadNurseryObjects mark;
             rt->gc.storeBuffer.putGeneric(mark);
             rt->jitRuntime()->setHasIonNurseryObjects(true);
         }
 
         if (!StartOffThreadIonCompile(cx, builder)) {
             JitSpew(JitSpew_IonAbort, "Unable to start off-thread ion compilation.");
-            builder->graphSpewer().endFunction();
             return AbortReason_Alloc;
         }
 
         // The allocator and associated data will be destroyed after being
         // processed in the finishedOffThreadCompilations list.
         autoDelete.forget();
 
         return AbortReason_NoAbort;
     }
 
-    // See PrepareForDebuggerOnIonCompilationHook
-    bool callOnIonCompilation = false;
-    AutoScriptVector debugScripts(cx);
-    LSprinter debugPrinter(builder->alloc().lifoAlloc());
-
-    ScopedJSDeletePtr<CodeGenerator> codegen;
-    {
-        AutoEnterAnalysis enter(cx);
-        codegen = CompileBackEnd(builder);
-        if (!codegen) {
-            JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
-            return AbortReason_Disable;
-        }
-
-        succeeded = codegen->link(cx, builder->constraints());
-        callOnIonCompilation = PrepareForDebuggerOnIonCompilationHook(
-            cx, succeeded, builder->graph(), &debugScripts, &debugPrinter);
+    ScopedJSDeletePtr<CodeGenerator> codegen(CompileBackEnd(builder));
+    if (!codegen) {
+        JitSpew(JitSpew_IonAbort, "Failed during back-end compilation.");
+        return AbortReason_Disable;
     }
 
-    if (callOnIonCompilation)
-        Debugger::onIonCompilation(cx, debugScripts, debugPrinter);
-
-    if (succeeded)
+    bool success = codegen->link(cx, builder->constraints());
+
+    if (success)
         return AbortReason_NoAbort;
     if (cx->isExceptionPending())
         return AbortReason_Error;
     return AbortReason_Disable;
 }
 
 static bool
 CheckFrame(JSContext* cx, BaselineFrame* frame)
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2822,47 +2822,54 @@ LinearSum::add(MDefinition* term, int32_
 
 bool
 LinearSum::add(int32_t constant)
 {
     return SafeAdd(constant, constant_, &constant_);
 }
 
 void
-LinearSum::dump(GenericPrinter& out) const
+LinearSum::print(Sprinter& sp) const
 {
     for (size_t i = 0; i < terms_.length(); i++) {
         int32_t scale = terms_[i].scale;
         int32_t id = terms_[i].term->id();
         MOZ_ASSERT(scale);
         if (scale > 0) {
             if (i)
-                out.printf("+");
+                sp.printf("+");
             if (scale == 1)
-                out.printf("#%d", id);
+                sp.printf("#%d", id);
             else
-                out.printf("%d*#%d", scale, id);
+                sp.printf("%d*#%d", scale, id);
         } else if (scale == -1) {
-            out.printf("-#%d", id);
+            sp.printf("-#%d", id);
         } else {
-            out.printf("%d*#%d", scale, id);
+            sp.printf("%d*#%d", scale, id);
         }
     }
     if (constant_ > 0)
-        out.printf("+%d", constant_);
+        sp.printf("+%d", constant_);
     else if (constant_ < 0)
-        out.printf("%d", constant_);
+        sp.printf("%d", constant_);
+}
+
+void
+LinearSum::dump(FILE* fp) const
+{
+    Sprinter sp(GetJitContext()->cx);
+    sp.init();
+    print(sp);
+    fprintf(fp, "%s\n", sp.string());
 }
 
 void
 LinearSum::dump() const
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 MDefinition*
 jit::ConvertLinearSum(TempAllocator& alloc, MBasicBlock* block, const LinearSum& sum, bool convertConstant)
 {
     MDefinition* def = nullptr;
 
     for (size_t i = 0; i < sum.numTerms(); i++) {
--- a/js/src/jit/IonAnalysis.h
+++ b/js/src/jit/IonAnalysis.h
@@ -143,17 +143,18 @@ class LinearSum
     // it can still be used.
     bool divide(int32_t scale);
 
     int32_t constant() const { return constant_; }
     size_t numTerms() const { return terms_.length(); }
     LinearTerm term(size_t i) const { return terms_[i]; }
     void replaceTerm(size_t i, MDefinition* def) { terms_[i].term = def; }
 
-    void dump(GenericPrinter& out) const;
+    void print(Sprinter& sp) const;
+    void dump(FILE*) const;
     void dump() const;
 
   private:
     Vector<LinearTerm, 2, JitAllocPolicy> terms_;
     int32_t constant_;
 };
 
 // Convert all components of a linear sum (except, optionally, the constant)
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -866,16 +866,17 @@ class IonBuilder
                                   const Class* clasp4 = nullptr);
     InliningStatus inlineIsConstructing(CallInfo& callInfo);
     InliningStatus inlineSubstringKernel(CallInfo& callInfo);
 
     // Testing functions.
     InliningStatus inlineBailout(CallInfo& callInfo);
     InliningStatus inlineAssertFloat32(CallInfo& callInfo);
     InliningStatus inlineAssertRecoveredOnBailout(CallInfo& callInfo);
+    InliningStatus inlineTrue(CallInfo& callInfo);
 
     // Bind function.
     InliningStatus inlineBoundFunction(CallInfo& callInfo, JSFunction* target);
 
     // Main inlining functions
     InliningStatus inlineNativeCall(CallInfo& callInfo, JSFunction* target);
     InliningStatus inlineNativeGetter(CallInfo& callInfo, JSFunction* target);
     InliningStatus inlineNonFunctionCall(CallInfo& callInfo, JSObject* target);
--- a/js/src/jit/JSONSpewer.cpp
+++ b/js/src/jit/JSONSpewer.cpp
@@ -12,147 +12,190 @@
 #include "jit/LIR.h"
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 #include "jit/RangeAnalysis.h"
 
 using namespace js;
 using namespace js::jit;
 
+JSONSpewer::~JSONSpewer()
+{
+    if (fp_)
+        fclose(fp_);
+}
+
 void
 JSONSpewer::indent()
 {
+    if (!fp_)
+        return;
     MOZ_ASSERT(indentLevel_ >= 0);
-    out_.printf("\n");
+    fprintf(fp_, "\n");
     for (int i = 0; i < indentLevel_; i++)
-        out_.printf("  ");
+        fprintf(fp_, "  ");
 }
 
 void
 JSONSpewer::property(const char* name)
 {
+    if (!fp_)
+        return;
+
     if (!first_)
-        out_.printf(",");
+        fprintf(fp_, ",");
     indent();
-    out_.printf("\"%s\":", name);
+    fprintf(fp_, "\"%s\":", name);
     first_ = false;
 }
 
 void
 JSONSpewer::beginObject()
 {
+    if (!fp_)
+        return;
+
     if (!first_) {
-        out_.printf(",");
+        fprintf(fp_, ",");
         indent();
     }
-    out_.printf("{");
+    fprintf(fp_, "{");
     indentLevel_++;
     first_ = true;
 }
 
 void
 JSONSpewer::beginObjectProperty(const char* name)
 {
+    if (!fp_)
+        return;
+
     property(name);
-    out_.printf("{");
+    fprintf(fp_, "{");
     indentLevel_++;
     first_ = true;
 }
 
 void
 JSONSpewer::beginListProperty(const char* name)
 {
-    property(name);
-    out_.printf("[");
-    first_ = true;
-}
+    if (!fp_)
+        return;
 
-void
-JSONSpewer::beginStringProperty(const char* name)
-{
     property(name);
-    out_.printf("\"");
-}
-
-void
-JSONSpewer::endStringProperty()
-{
-    out_.printf("\"");
+    fprintf(fp_, "[");
+    first_ = true;
 }
 
 void
 JSONSpewer::stringProperty(const char* name, const char* format, ...)
 {
+    if (!fp_)
+        return;
+
     va_list ap;
     va_start(ap, format);
 
-    beginStringProperty(name);
-    out_.vprintf(format, ap);
-    endStringProperty();
+    property(name);
+    fprintf(fp_, "\"");
+    vfprintf(fp_, format, ap);
+    fprintf(fp_, "\"");
 
     va_end(ap);
 }
 
 void
 JSONSpewer::stringValue(const char* format, ...)
 {
+    if (!fp_)
+        return;
+
     va_list ap;
     va_start(ap, format);
 
     if (!first_)
-        out_.printf(",");
-    out_.printf("\"");
-    out_.vprintf(format, ap);
-    out_.printf("\"");
+        fprintf(fp_, ",");
+    fprintf(fp_, "\"");
+    vfprintf(fp_, format, ap);
+    fprintf(fp_, "\"");
 
     va_end(ap);
     first_ = false;
 }
 
 void
 JSONSpewer::integerProperty(const char* name, int value)
 {
+    if (!fp_)
+        return;
+
     property(name);
-    out_.printf("%d", value);
+    fprintf(fp_, "%d", value);
 }
 
 void
 JSONSpewer::integerValue(int value)
 {
+    if (!fp_)
+        return;
+
     if (!first_)
-        out_.printf(",");
-    out_.printf("%d", value);
+        fprintf(fp_, ",");
+    fprintf(fp_, "%d", value);
     first_ = false;
 }
 
 void
 JSONSpewer::endObject()
 {
+    if (!fp_)
+        return;
+
     indentLevel_--;
     indent();
-    out_.printf("}");
+    fprintf(fp_, "}");
     first_ = false;
 }
 
 void
 JSONSpewer::endList()
 {
-    out_.printf("]");
+    if (!fp_)
+        return;
+
+    fprintf(fp_, "]");
     first_ = false;
 }
 
+bool
+JSONSpewer::init(const char* path)
+{
+    fp_ = fopen(path, "w");
+    if (!fp_)
+        return false;
+
+    beginObject();
+    beginListProperty("functions");
+    return true;
+}
+
 void
 JSONSpewer::beginFunction(JSScript* script)
 {
+    if (inFunction_)
+        endFunction();
+
     beginObject();
     if (script)
         stringProperty("name", "%s:%d", script->filename(), script->lineno());
     else
         stringProperty("name", "asm.js compilation");
     beginListProperty("passes");
+
+    inFunction_ = true;
 }
 
 void
 JSONSpewer::beginPass(const char* pass)
 {
     beginObject();
     stringProperty("name", pass);
 }
@@ -166,23 +209,23 @@ JSONSpewer::spewMResumePoint(MResumePoin
     beginObjectProperty("resumePoint");
 
     if (rp->caller())
         integerProperty("caller", rp->caller()->block()->id());
 
     property("mode");
     switch (rp->mode()) {
       case MResumePoint::ResumeAt:
-        out_.printf("\"At\"");
+        fprintf(fp_, "\"At\"");
         break;
       case MResumePoint::ResumeAfter:
-        out_.printf("\"After\"");
+        fprintf(fp_, "\"After\"");
         break;
       case MResumePoint::Outer:
-        out_.printf("\"Outer\"");
+        fprintf(fp_, "\"Outer\"");
         break;
     }
 
     beginListProperty("operands");
     for (MResumePoint* iter = rp; iter; iter = iter->caller()) {
         for (int i = iter->numOperands() - 1; i >= 0; i--)
             integerValue(iter->getOperand(i)->id());
         if (iter->caller())
@@ -196,19 +239,19 @@ JSONSpewer::spewMResumePoint(MResumePoin
 void
 JSONSpewer::spewMDef(MDefinition* def)
 {
     beginObject();
 
     integerProperty("id", def->id());
 
     property("opcode");
-    out_.printf("\"");
-    def->printOpcode(out_);
-    out_.printf("\"");
+    fprintf(fp_, "\"");
+    def->printOpcode(fp_);
+    fprintf(fp_, "\"");
 
     beginListProperty("attributes");
 #define OUTPUT_ATTRIBUTE(X) do{ if(def->is##X()) stringValue(#X); } while(0);
     MIR_FLAG_LIST(OUTPUT_ATTRIBUTE);
 #undef OUTPUT_ATTRIBUTE
     endList();
 
     beginListProperty("inputs");
@@ -228,35 +271,38 @@ JSONSpewer::spewMDef(MDefinition* def)
         endList();
     }
 
     bool isTruncated = false;
     if (def->isAdd() || def->isSub() || def->isMod() || def->isMul() || def->isDiv())
         isTruncated = static_cast<MBinaryArithInstruction*>(def)->isTruncated();
 
     if (def->type() != MIRType_None && def->range()) {
-        beginStringProperty("type");
-        def->range()->dump(out_);
-        out_.printf(" : %s%s", StringFromMIRType(def->type()), (isTruncated ? " (t)" : ""));
-        endStringProperty();
+        Sprinter sp(GetJitContext()->cx);
+        sp.init();
+        def->range()->print(sp);
+        stringProperty("type", "%s : %s%s", sp.string(), StringFromMIRType(def->type()), (isTruncated ? " (t)" : ""));
     } else {
         stringProperty("type", "%s%s", StringFromMIRType(def->type()), (isTruncated ? " (t)" : ""));
     }
 
     if (def->isInstruction()) {
         if (MResumePoint* rp = def->toInstruction()->resumePoint())
             spewMResumePoint(rp);
     }
 
     endObject();
 }
 
 void
 JSONSpewer::spewMIR(MIRGraph* mir)
 {
+    if (!fp_)
+        return;
+
     beginObjectProperty("mir");
     beginListProperty("blocks");
 
     for (MBasicBlockIterator block(mir->begin()); block != mir->end(); block++) {
         beginObject();
 
         integerProperty("number", block->id());
 
@@ -293,36 +339,42 @@ JSONSpewer::spewMIR(MIRGraph* mir)
 
     endList();
     endObject();
 }
 
 void
 JSONSpewer::spewLIns(LNode* ins)
 {
+    if (!fp_)
+        return;
+
     beginObject();
 
     integerProperty("id", ins->id());
 
     property("opcode");
-    out_.printf("\"");
-    ins->dump(out_);
-    out_.printf("\"");
+    fprintf(fp_, "\"");
+    ins->dump(fp_);
+    fprintf(fp_, "\"");
 
     beginListProperty("defs");
     for (size_t i = 0; i < ins->numDefs(); i++)
         integerValue(ins->getDef(i)->virtualRegister());
     endList();
 
     endObject();
 }
 
 void
 JSONSpewer::spewLIR(MIRGraph* mir)
 {
+    if (!fp_)
+        return;
+
     beginObjectProperty("lir");
     beginListProperty("blocks");
 
     for (MBasicBlockIterator i(mir->begin()); i != mir->end(); i++) {
         LBlock* block = i->lir();
         if (!block)
             continue;
 
@@ -341,16 +393,19 @@ JSONSpewer::spewLIR(MIRGraph* mir)
 
     endList();
     endObject();
 }
 
 void
 JSONSpewer::spewIntervals(BacktrackingAllocator* regalloc)
 {
+    if (!fp_)
+        return;
+
     beginObjectProperty("intervals");
     beginListProperty("blocks");
 
     for (size_t bno = 0; bno < regalloc->graph.numBlocks(); bno++) {
         beginObject();
         integerProperty("number", bno);
         beginListProperty("vregs");
 
@@ -365,17 +420,17 @@ JSONSpewer::spewIntervals(BacktrackingAl
                 beginListProperty("intervals");
 
                 for (size_t i = 0; i < vreg->numIntervals(); i++) {
                     LiveInterval* live = vreg->getInterval(i);
 
                     if (live->numRanges()) {
                         beginObject();
                         property("allocation");
-                        out_.printf("\"%s\"", live->getAllocation()->toString());
+                        fprintf(fp_, "\"%s\"", live->getAllocation()->toString());
                         beginListProperty("ranges");
 
                         for (size_t j = 0; j < live->numRanges(); j++) {
                             beginObject();
                             integerProperty("start", live->getRange(j)->from.bits());
                             integerProperty("end", live->getRange(j)->to.bits());
                             endObject();
                         }
@@ -397,25 +452,38 @@ JSONSpewer::spewIntervals(BacktrackingAl
     endList();
     endObject();
 }
 
 void
 JSONSpewer::endPass()
 {
     endObject();
+    fflush(fp_);
 }
 
 void
 JSONSpewer::endFunction()
 {
+    MOZ_ASSERT(inFunction_);
     endList();
     endObject();
+    fflush(fp_);
+    inFunction_ = false;
 }
 
 void
-JSONSpewer::spewDebuggerGraph(MIRGraph* graph)
+JSONSpewer::finish()
 {
-    beginObject();
-    spewMIR(graph);
-    spewLIR(graph);
+    if (!fp_)
+        return;
+
+    if (inFunction_)
+        endFunction();
+
+    endList();
     endObject();
+    fprintf(fp_, "\n");
+
+    fclose(fp_);
+    fp_ = nullptr;
 }
+
--- a/js/src/jit/JSONSpewer.h
+++ b/js/src/jit/JSONSpewer.h
@@ -5,66 +5,69 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_JSONSpewer_h
 #define jit_JSONSpewer_h
 
 #include <stdio.h>
 
 #include "js/TypeDecls.h"
-#include "vm/Printer.h"
 
 namespace js {
 namespace jit {
 
 class BacktrackingAllocator;
 class MDefinition;
 class MIRGraph;
 class MResumePoint;
 class LNode;
 
 class JSONSpewer
 {
   private:
+    // Set by beginFunction(); unset by endFunction().
+    // Used to correctly format output in case of abort during compilation.
+    bool inFunction_;
+
     int indentLevel_;
     bool first_;
-    GenericPrinter& out_;
+    FILE* fp_;
 
     void indent();
 
     void property(const char* name);
     void beginObject();
     void beginObjectProperty(const char* name);
     void beginListProperty(const char* name);
     void stringValue(const char* format, ...);
     void stringProperty(const char* name, const char* format, ...);
-    void beginStringProperty(const char* name);
-    void endStringProperty();
     void integerValue(int value);
     void integerProperty(const char* name, int value);
     void endObject();
     void endList();
 
   public:
-    explicit JSONSpewer(GenericPrinter& out)
-      : indentLevel_(0),
+    JSONSpewer()
+      : inFunction_(false),
+        indentLevel_(0),
         first_(true),
-        out_(out)
+        fp_(nullptr)
     { }
+    ~JSONSpewer();
 
+    bool init(const char* path);
     void beginFunction(JSScript* script);
     void beginPass(const char * pass);
     void spewMDef(MDefinition* def);
     void spewMResumePoint(MResumePoint* rp);
     void spewMIR(MIRGraph* mir);
     void spewLIns(LNode* ins);
     void spewLIR(MIRGraph* mir);
     void spewIntervals(BacktrackingAllocator* regalloc);
     void endPass();
     void endFunction();
-
-    void spewDebuggerGraph(MIRGraph* mir);
+    void finish();
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_JSONSpewer_h */
--- a/js/src/jit/JitSpewer.cpp
+++ b/js/src/jit/JitSpewer.cpp
@@ -3,18 +3,16 @@
  * 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/. */
 
 #ifdef DEBUG
 
 #include "jit/JitSpewer.h"
 
-#include "mozilla/Atomics.h"
-
 #include "jit/Ion.h"
 #include "jit/MIR.h"
 
 #include "vm/HelperThreads.h"
 
 #ifndef JIT_SPEW_DIR
 # if defined(_WIN32)
 #  define JIT_SPEW_DIR ""
@@ -23,87 +21,32 @@
 # else
 #  define JIT_SPEW_DIR "/tmp/"
 # endif
 #endif
 
 using namespace js;
 using namespace js::jit;
 
-class IonSpewer
-{
-  private:
-    PRLock* outputLock_;
-    Fprinter c1Output_;
-    Fprinter jsonOutput_;
-    bool firstFunction_;
-    bool asyncLogging_;
-    bool inited_;
-
-    void release();
-
-  public:
-    IonSpewer()
-      : firstFunction_(false),
-        asyncLogging_(false),
-        inited_(false)
-    { }
-
-    // File output is terminated safely upon destruction.
-    ~IonSpewer();
-
-    bool init();
-    bool isEnabled() {
-        return inited_;
-    }
-    void setAsyncLogging(bool incremental) {
-        asyncLogging_ = incremental;
-    }
-    bool getAsyncLogging() {
-        return asyncLogging_;
-    }
-
-    void beginFunction();
-    void spewPass(GraphSpewer* gs);
-    void endFunction(GraphSpewer* gs);
-
-    // Lock used to sequentialized asynchronous compilation output.
-    void lockOutput() {
-        PR_Lock(outputLock_);
-    }
-    void unlockOutput() {
-        PR_Unlock(outputLock_);
-    }
-};
-
-class AutoLockIonSpewerOutput
-{
-  private:
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-  public:
-    explicit AutoLockIonSpewerOutput(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-    ~AutoLockIonSpewerOutput();
-};
-
 // IonSpewer singleton.
 static IonSpewer ionspewer;
 
 static bool LoggingChecked = false;
 static uint32_t LoggingBits = 0;
-static mozilla::Atomic<uint32_t, mozilla::Relaxed> filteredOutCompilations(0);
+static uint32_t filteredOutCompilations = 0;
 
 static const char * const ChannelNames[] =
 {
 #define JITSPEW_CHANNEL(name) #name,
     JITSPEW_CHANNEL_LIST(JITSPEW_CHANNEL)
 #undef JITSPEW_CHANNEL
 };
 
 static bool
-FilterContainsLocation(JSScript* function)
+FilterContainsLocation(HandleScript function)
 {
     static const char* filter = getenv("IONFILTER");
 
     // If there is no filter we accept all outputs.
     if (!filter || !filter[0])
         return true;
 
     // Disable asm.js output when filter is set.
@@ -125,234 +68,147 @@ FilterContainsLocation(JSScript* functio
             }
         }
         index = strstr(index + filelen, filename);
     }
     return false;
 }
 
 void
-jit::EnableIonDebugSyncLogging()
+jit::EnableIonDebugLogging()
 {
+    EnableChannel(JitSpew_IonLogs);
     ionspewer.init();
-    ionspewer.setAsyncLogging(false);
-    EnableChannel(JitSpew_IonSyncLogs);
+}
+
+void
+jit::IonSpewNewFunction(MIRGraph* graph, HandleScript func)
+{
+    if (GetJitContext()->runtime->onMainThread())
+        ionspewer.beginFunction(graph, func);
+}
+
+void
+jit::IonSpewPass(const char* pass)
+{
+    if (GetJitContext()->runtime->onMainThread())
+        ionspewer.spewPass(pass);
 }
 
 void
-jit::EnableIonDebugAsyncLogging()
+jit::IonSpewPass(const char* pass, BacktrackingAllocator* ra)
 {
-    ionspewer.init();
-    ionspewer.setAsyncLogging(true);
+    if (GetJitContext()->runtime->onMainThread())
+        ionspewer.spewPass(pass, ra);
 }
 
 void
-IonSpewer::release()
+jit::IonSpewEndFunction()
 {
-    if (c1Output_.isInitialized())
-        c1Output_.finish();
-    if (jsonOutput_.isInitialized())
-        jsonOutput_.finish();
-    if (outputLock_)
-        PR_DestroyLock(outputLock_);
-    outputLock_ = nullptr;
-    inited_ = false;
+    if (GetJitContext()->runtime->onMainThread())
+        ionspewer.endFunction();
+}
+
+
+IonSpewer::~IonSpewer()
+{
+    if (!inited_)
+        return;
+
+    c1Spewer.finish();
+    jsonSpewer.finish();
 }
 
 bool
 IonSpewer::init()
 {
     if (inited_)
         return true;
 
-    outputLock_ = PR_NewLock();
-    if (!outputLock_ ||
-        !c1Output_.init(JIT_SPEW_DIR "ion.cfg") ||
-        !jsonOutput_.init(JIT_SPEW_DIR "ion.json"))
-    {
-        release();
+    if (!c1Spewer.init(JIT_SPEW_DIR "ion.cfg"))
         return false;
-    }
-
-    jsonOutput_.printf("{\n  \"functions\": [\n");
-    firstFunction_ = true;
+    if (!jsonSpewer.init(JIT_SPEW_DIR "ion.json"))
+        return false;
 
     inited_ = true;
     return true;
 }
 
-AutoLockIonSpewerOutput::AutoLockIonSpewerOutput(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+bool
+IonSpewer::isSpewingFunction() const
 {
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    ionspewer.lockOutput();
-}
-
-AutoLockIonSpewerOutput::~AutoLockIonSpewerOutput()
-{
-    ionspewer.unlockOutput();
+    return inited_ && graph;
 }
 
 void
-IonSpewer::beginFunction()
-{
-    // If we are doing a synchronous logging then we spew everything as we go,
-    // as this is useful in case of failure during the compilation. On the other
-    // hand, it is recommended to disabled off main thread compilation.
-    if (!getAsyncLogging() && !firstFunction_) {
-        AutoLockIonSpewerOutput outputLock;
-        jsonOutput_.put(","); // separate functions
-    }
-}
-
-void
-IonSpewer::spewPass(GraphSpewer* gs)
-{
-    if (!getAsyncLogging()) {
-        AutoLockIonSpewerOutput outputLock;
-        gs->dump(c1Output_, jsonOutput_);
-    }
-}
-
-void
-IonSpewer::endFunction(GraphSpewer* gs)
-{
-    AutoLockIonSpewerOutput outputLock;
-    if (getAsyncLogging() && !firstFunction_)
-        jsonOutput_.put(","); // separate functions
-
-    gs->dump(c1Output_, jsonOutput_);
-    firstFunction_ = false;
-}
-
-IonSpewer::~IonSpewer()
+IonSpewer::beginFunction(MIRGraph* graph, HandleScript function)
 {
     if (!inited_)
         return;
 
-    jsonOutput_.printf("\n]}\n");
-    release();
-}
-
-void
-GraphSpewer::init(MIRGraph* graph, JSScript* function)
-{
-    MOZ_ASSERT(!isSpewing());
-    if (!ionspewer.isEnabled())
-        return;
-
     if (!FilterContainsLocation(function)) {
+        MOZ_ASSERT(!this->graph);
         // filter out logs during the compilation.
         filteredOutCompilations++;
-        MOZ_ASSERT(!isSpewing());
         return;
     }
 
-    graph_ = graph;
-    MOZ_ASSERT(isSpewing());
+    this->graph = graph;
+
+    c1Spewer.beginFunction(graph, function);
+    jsonSpewer.beginFunction(function);
 }
 
 void
-GraphSpewer::beginFunction(JSScript* function)
+IonSpewer::spewPass(const char* pass)
 {
-    if (!isSpewing())
+    if (!isSpewingFunction())
         return;
 
-    c1Spewer_.beginFunction(graph_, function);
-    jsonSpewer_.beginFunction(function);
-
-    ionspewer.beginFunction();
-}
-
-void
-GraphSpewer::spewPass(const char* pass)
-{
-    if (!isSpewing())
-        return;
-
-    c1Spewer_.spewPass(pass);
-
-    jsonSpewer_.beginPass(pass);
-    jsonSpewer_.spewMIR(graph_);
-    jsonSpewer_.spewLIR(graph_);
-    jsonSpewer_.endPass();
-
-    ionspewer.spewPass(this);
+    c1Spewer.spewPass(pass);
+    jsonSpewer.beginPass(pass);
+    jsonSpewer.spewMIR(graph);
+    jsonSpewer.spewLIR(graph);
+    jsonSpewer.endPass();
 }
 
 void
-GraphSpewer::spewPass(const char* pass, BacktrackingAllocator* ra)
+IonSpewer::spewPass(const char* pass, BacktrackingAllocator* ra)
 {
-    if (!isSpewing())
+    if (!isSpewingFunction())
         return;
 
-    c1Spewer_.spewPass(pass);
-    c1Spewer_.spewIntervals(pass, ra);
-
-    jsonSpewer_.beginPass(pass);
-    jsonSpewer_.spewMIR(graph_);
-    jsonSpewer_.spewLIR(graph_);
-    jsonSpewer_.spewIntervals(ra);
-    jsonSpewer_.endPass();
-
-    ionspewer.spewPass(this);
+    c1Spewer.spewPass(pass);
+    c1Spewer.spewIntervals(pass, ra);
+    jsonSpewer.beginPass(pass);
+    jsonSpewer.spewMIR(graph);
+    jsonSpewer.spewLIR(graph);
+    jsonSpewer.spewIntervals(ra);
+    jsonSpewer.endPass();
 }
 
 void
-GraphSpewer::endFunction()
+IonSpewer::endFunction()
 {
-    if (!ionspewer.isEnabled())
-        return;
-
-    if (!isSpewing()) {
-        MOZ_ASSERT(filteredOutCompilations != 0);
-        filteredOutCompilations--;
+    if (!isSpewingFunction()) {
+        if (inited_) {
+            MOZ_ASSERT(filteredOutCompilations != 0);
+            filteredOutCompilations--;
+        }
         return;
     }
 
-    c1Spewer_.endFunction();
-    jsonSpewer_.endFunction();
-
-    ionspewer.endFunction(this);
-    graph_ = nullptr;
-}
+    c1Spewer.endFunction();
+    jsonSpewer.endFunction();
 
-void
-GraphSpewer::dump(Fprinter& c1Out, Fprinter& jsonOut)
-{
-    if (!c1Printer_.hadOutOfMemory())
-        c1Printer_.exportInto(c1Out);
-    c1Printer_.clear();
-
-    if (!jsonPrinter_.hadOutOfMemory())
-        jsonPrinter_.exportInto(jsonOut);
-    else
-        jsonOut.put("{}");
-    jsonPrinter_.clear();
+    this->graph = nullptr;
 }
 
-void
-jit::SpewBeginFunction(MIRGenerator* mir, JSScript* function)
-{
-    MIRGraph* graph = &mir->graph();
-    mir->graphSpewer().init(graph, function);
-    mir->graphSpewer().beginFunction(function);
-}
 
-AutoSpewEndFunction::~AutoSpewEndFunction()
-{
-    mir_->graphSpewer().endFunction();
-}
-
-Fprinter&
-jit::JitSpewPrinter()
-{
-    static Fprinter out;
-    return out;
-}
-
+FILE* jit::JitSpewFile = nullptr;
 
 static bool
 ContainsFlag(const char* str, const char* flag)
 {
     size_t flaglen = strlen(flag);
     const char* index = strstr(str, flag);
     while (index) {
         if ((index == str || index[-1] == ',') && (index[flaglen] == 0 || index[flaglen] == ','))
@@ -393,17 +249,16 @@ jit::CheckLogging()
             "  caches     Inline caches\n"
             "  osi        Invalidation\n"
             "  safepoints Safepoints\n"
             "  pools      Literal Pools (ARM only for now)\n"
             "  cacheflush Instruction Cache flushes (ARM only for now)\n"
             "  range      Range Analysis\n"
             "  unroll     Loop unrolling\n"
             "  logs       C1 and JSON visualization logging\n"
-            "  logs-sync  Same as logs, but flushes between each pass (sync. compiled functions only).\n"
             "  profiling  Profiling-related information\n"
             "  trackopts  Optimization tracking information\n"
             "  all        Everything\n"
             "\n"
             "  bl-aborts  Baseline compiler abort messages\n"
             "  bl-scripts Baseline script-compilation\n"
             "  bl-op      Baseline compiler detailed op-specific messages\n"
             "  bl-ic      Baseline inline-cache messages\n"
@@ -453,19 +308,17 @@ jit::CheckLogging()
         EnableChannel(JitSpew_IonIC);
     if (ContainsFlag(env, "safepoints"))
         EnableChannel(JitSpew_Safepoints);
     if (ContainsFlag(env, "pools"))
         EnableChannel(JitSpew_Pools);
     if (ContainsFlag(env, "cacheflush"))
         EnableChannel(JitSpew_CacheFlush);
     if (ContainsFlag(env, "logs"))
-        EnableIonDebugAsyncLogging();
-    if (ContainsFlag(env, "logs-sync"))
-        EnableIonDebugSyncLogging();
+        EnableIonDebugLogging();
     if (ContainsFlag(env, "profiling"))
         EnableChannel(JitSpew_Profiling);
     if (ContainsFlag(env, "trackopts"))
         EnableChannel(JitSpew_OptimizationTracking);
     if (ContainsFlag(env, "all"))
         LoggingBits = uint32_t(-1);
 
     if (ContainsFlag(env, "bl-aborts"))
@@ -490,17 +343,17 @@ jit::CheckLogging()
         EnableChannel(JitSpew_BaselineOp);
         EnableChannel(JitSpew_BaselineIC);
         EnableChannel(JitSpew_BaselineICFallback);
         EnableChannel(JitSpew_BaselineOSR);
         EnableChannel(JitSpew_BaselineBailouts);
         EnableChannel(JitSpew_BaselineDebugModeOSR);
     }
 
-    JitSpewPrinter().init(stderr);
+    JitSpewFile = stderr;
 }
 
 void
 jit::JitSpewStartVA(JitSpewChannel channel, const char* fmt, va_list ap)
 {
     if (!JitSpewEnabled(channel))
         return;
 
@@ -544,20 +397,19 @@ jit::JitSpew(JitSpewChannel channel, con
 
 void
 jit::JitSpewDef(JitSpewChannel channel, const char* str, MDefinition* def)
 {
     if (!JitSpewEnabled(channel))
         return;
 
     JitSpewHeader(channel);
-    Fprinter& out = JitSpewPrinter();
-    out.put(str);
-    def->dump(out);
-    def->dumpLocation(out);
+    fprintf(JitSpewFile, "%s", str);
+    def->dump(JitSpewFile);
+    def->dumpLocation(JitSpewFile);
 }
 
 void
 jit::JitSpewStart(JitSpewChannel channel, const char* fmt, ...)
 {
     va_list ap;
     va_start(ap, fmt);
     JitSpewStartVA(channel, fmt, ap);
@@ -597,10 +449,20 @@ jit::EnableChannel(JitSpewChannel channe
 
 void
 jit::DisableChannel(JitSpewChannel channel)
 {
     MOZ_ASSERT(LoggingChecked);
     LoggingBits &= ~(1 << uint32_t(channel));
 }
 
+IonSpewFunction::IonSpewFunction(MIRGraph* graph, JS::HandleScript function)
+{
+    IonSpewNewFunction(graph, function);
+}
+
+IonSpewFunction::~IonSpewFunction()
+{
+    IonSpewEndFunction();
+}
+
 #endif /* DEBUG */
 
--- a/js/src/jit/JitSpewer.h
+++ b/js/src/jit/JitSpewer.h
@@ -72,17 +72,17 @@ namespace jit {
                                             \
     /* ION COMPILER SPEW */                 \
                                             \
     /* Used to abort SSA construction */    \
     _(IonAbort)                             \
     /* Information about compiled scripts */\
     _(IonScripts)                           \
     /* Info about failing to log script */  \
-    _(IonSyncLogs)                          \
+    _(IonLogs)                              \
     /* Information during MIR building */   \
     _(IonMIR)                               \
     /* Information during bailouts */       \
     _(IonBailouts)                          \
     /* Information during OSI */            \
     _(IonInvalidate)                        \
     /* Debug info about snapshots */        \
     _(IonSnapshots)                         \
@@ -98,111 +98,83 @@ enum JitSpewChannel {
 
 
 // The JitSpewer is only available on debug builds.
 // None of the global functions have effect on non-debug builds.
 static const int NULL_ID = -1;
 
 #ifdef DEBUG
 
-// Class made to hold the MIR and LIR graphs of an AsmJS / Ion compilation.
-class GraphSpewer
+class IonSpewer
 {
   private:
-    MIRGraph* graph_;
-    LSprinter c1Printer_;
-    LSprinter jsonPrinter_;
-    C1Spewer c1Spewer_;
-    JSONSpewer jsonSpewer_;
+    MIRGraph* graph;
+    C1Spewer c1Spewer;
+    JSONSpewer jsonSpewer;
+    bool inited_;
 
   public:
-    explicit GraphSpewer(TempAllocator *alloc)
-      : graph_(nullptr),
-        c1Printer_(alloc->lifoAlloc()),
-        jsonPrinter_(alloc->lifoAlloc()),
-        c1Spewer_(c1Printer_),
-        jsonSpewer_(jsonPrinter_)
+    IonSpewer()
+      : graph(nullptr), inited_(false)
     { }
 
-    bool isSpewing() const {
-        return graph_;
-    }
-    void init(MIRGraph* graph, JSScript* function);
-    void beginFunction(JSScript* function);
+    // File output is terminated safely upon destruction.
+    ~IonSpewer();
+
+    bool init();
+    void beginFunction(MIRGraph* graph, JS::HandleScript);
+    bool isSpewingFunction() const;
     void spewPass(const char* pass);
     void spewPass(const char* pass, BacktrackingAllocator* ra);
     void endFunction();
-
-    void dump(Fprinter& c1, Fprinter& json);
 };
 
-void SpewBeginFunction(MIRGenerator* mir, JSScript* function);
-class AutoSpewEndFunction
+class IonSpewFunction
 {
-  private:
-    MIRGenerator* mir_;
-
   public:
-    explicit AutoSpewEndFunction(MIRGenerator* mir)
-      : mir_(mir)
-    { }
-    ~AutoSpewEndFunction();
+    IonSpewFunction(MIRGraph* graph, JS::HandleScript function);
+    ~IonSpewFunction();
 };
 
+void IonSpewNewFunction(MIRGraph* graph, JS::HandleScript function);
+void IonSpewPass(const char* pass);
+void IonSpewPass(const char* pass, BacktrackingAllocator* ra);
+void IonSpewEndFunction();
+
 void CheckLogging();
-Fprinter& JitSpewPrinter();
+extern FILE* JitSpewFile;
 void JitSpew(JitSpewChannel channel, const char* fmt, ...);
 void JitSpewStart(JitSpewChannel channel, const char* fmt, ...);
 void JitSpewCont(JitSpewChannel channel, const char* fmt, ...);
 void JitSpewFin(JitSpewChannel channel);
 void JitSpewHeader(JitSpewChannel channel);
 bool JitSpewEnabled(JitSpewChannel channel);
 void JitSpewVA(JitSpewChannel channel, const char* fmt, va_list ap);
 void JitSpewStartVA(JitSpewChannel channel, const char* fmt, va_list ap);
 void JitSpewContVA(JitSpewChannel channel, const char* fmt, va_list ap);
 void JitSpewDef(JitSpewChannel channel, const char* str, MDefinition* def);
 
 void EnableChannel(JitSpewChannel channel);
 void DisableChannel(JitSpewChannel channel);
-void EnableIonDebugSyncLogging();
-void EnableIonDebugAsyncLogging();
+void EnableIonDebugLogging();
 
 #else
 
-class GraphSpewer
-{
-  public:
-    GraphSpewer(TempAllocator *alloc) { }
-
-    bool isSpewing() { return false; }
-    void init(MIRGraph* graph, JSScript* function) { }
-    void beginFunction(JSScript* function) { }
-    void spewPass(const char* pass) { }
-    void spewPass(const char* pass, BacktrackingAllocator* ra) { }
-    void endFunction() { }
-
-    void dump(Fprinter& c1, Fprinter& json) { }
-};
-
-static inline void SpewBeginFunction(MIRGenerator* mir, JSScript* function)
+static inline void IonSpewNewFunction(MIRGraph* graph, JS::HandleScript function)
+{ }
+static inline void IonSpewPass(const char* pass)
 { }
-
-class AutoSpewEndFunction
-{
-  public:
-    explicit AutoSpewEndFunction(MIRGenerator* mir) { }
-    ~AutoSpewEndFunction() { }
-};
+static inline void IonSpewPass(const char* pass, BacktrackingAllocator* ra)
+{ }
+static inline void IonSpewEndFunction()
+{ }
 
 static inline void CheckLogging()
 { }
-static inline Fprinter& JitSpewPrinter()
-{
-    MOZ_CRASH("No empty backend for JitSpewPrinter");
-}
+static FILE* const JitSpewFile = nullptr;
 static inline void JitSpew(JitSpewChannel, const char* fmt, ...)
 { }
 static inline void JitSpewStart(JitSpewChannel channel, const char* fmt, ...)
 { }
 static inline void JitSpewCont(JitSpewChannel channel, const char* fmt, ...)
 { }
 static inline void JitSpewFin(JitSpewChannel channel)
 { }
@@ -215,19 +187,17 @@ static inline void JitSpewVA(JitSpewChan
 { }
 static inline void JitSpewDef(JitSpewChannel channel, const char* str, MDefinition* def)
 { }
 
 static inline void EnableChannel(JitSpewChannel)
 { }
 static inline void DisableChannel(JitSpewChannel)
 { }
-static inline void EnableIonDebugSyncLogging()
-{ }
-static inline void EnableIonDebugAsyncLogging()
+static inline void EnableIonDebugLogging()
 { }
 
 #endif /* DEBUG */
 
 template <JitSpewChannel Channel>
 class AutoDisableSpew
 {
     mozilla::DebugOnly<bool> enabled_;
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -101,17 +101,17 @@ class LMoveGroup : public LInstructionHe
 
   public:
     LIR_HEADER(MoveGroup)
 
     static LMoveGroup* New(TempAllocator& alloc) {
         return new(alloc) LMoveGroup(alloc);
     }
 
-    void printOperands(GenericPrinter& out);
+    void printOperands(FILE* fp);
 
     // Add a move which takes place simultaneously with all others in the group.
     bool add(LAllocation* from, LAllocation* to, LDefinition::Type type);
 
     // Add a move which takes place after existing moves in the group.
     bool addAfter(LAllocation* from, LAllocation* to, LDefinition::Type type);
 
     size_t numMoves() const {
--- a/js/src/jit/LIR.cpp
+++ b/js/src/jit/LIR.cpp
@@ -52,30 +52,28 @@ LIRGraph::noteNeedsSafepoint(LInstructio
     // Instructions with safepoints must be in linear order.
     MOZ_ASSERT_IF(!safepoints_.empty(), safepoints_.back()->id() < ins->id());
     if (!ins->isCall() && !nonCallSafepoints_.append(ins))
         return false;
     return safepoints_.append(ins);
 }
 
 void
-LIRGraph::dump(GenericPrinter& out)
+LIRGraph::dump(FILE* fp)
 {
     for (size_t i = 0; i < numBlocks(); i++) {
-        getBlock(i)->dump(out);
-        out.printf("\n");
+        getBlock(i)->dump(fp);
+        fprintf(fp, "\n");
     }
 }
 
 void
 LIRGraph::dump()
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 LBlock::LBlock(MBasicBlock* from)
   : block_(from),
     phis_(),
     entryMoveGroup_(nullptr),
     exitMoveGroup_(nullptr)
 {
@@ -147,35 +145,33 @@ LBlock::getExitMoveGroup(TempAllocator& 
     if (exitMoveGroup_)
         return exitMoveGroup_;
     exitMoveGroup_ = LMoveGroup::New(alloc);
     insertBefore(*rbegin(), exitMoveGroup_);
     return exitMoveGroup_;
 }
 
 void
-LBlock::dump(GenericPrinter& out)
+LBlock::dump(FILE* fp)
 {
-    out.printf("block%u:\n", mir()->id());
+    fprintf(fp, "block%u:\n", mir()->id());
     for (size_t i = 0; i < numPhis(); ++i) {
-        getPhi(i)->dump(out);
-        out.printf("\n");
+        getPhi(i)->dump(fp);
+        fprintf(fp, "\n");
     }
     for (LInstructionIterator iter = begin(); iter != end(); iter++) {
-        iter->dump(out);
-        out.printf("\n");
+        iter->dump(fp);
+        fprintf(fp, "\n");
     }
 }
 
 void
 LBlock::dump()
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 static size_t
 TotalOperandCount(LRecoverInfo* recoverInfo)
 {
     size_t accum = 0;
     for (LRecoverInfo::OperandIter it(recoverInfo); !it; ++it) {
         if (!it->isRecoveredOnBailout())
@@ -308,34 +304,34 @@ LSnapshot::rewriteRecoveredInput(LUse in
     // equal to the instruction's result.
     for (size_t i = 0; i < numEntries(); i++) {
         if (getEntry(i)->isUse() && getEntry(i)->toUse()->virtualRegister() == input.virtualRegister())
             setEntry(i, LUse(input.virtualRegister(), LUse::RECOVERED_INPUT));
     }
 }
 
 void
-LNode::printName(GenericPrinter& out, Opcode op)
+LNode::printName(FILE* fp, Opcode op)
 {
     static const char * const names[] =
     {
 #define LIROP(x) #x,
         LIR_OPCODE_LIST(LIROP)
 #undef LIROP
     };
     const char* name = names[op];
     size_t len = strlen(name);
     for (size_t i = 0; i < len; i++)
-        out.printf("%c", tolower(name[i]));
+        fprintf(fp, "%c", tolower(name[i]));
 }
 
 void
-LNode::printName(GenericPrinter& out)
+LNode::printName(FILE* fp)
 {
-    printName(out, op());
+    printName(fp, op());
 }
 
 bool
 LAllocation::aliases(const LAllocation& other) const
 {
     if (isFloatReg() && other.isFloatReg())
         return toFloatReg()->reg().aliases(other.toFloatReg()->reg());
     return *this == other;
@@ -455,87 +451,84 @@ LAllocation::dump() const
 
 void
 LDefinition::dump() const
 {
     fprintf(stderr, "%s\n", toString());
 }
 
 void
-LNode::printOperands(GenericPrinter& out)
+LNode::printOperands(FILE* fp)
 {
     for (size_t i = 0, e = numOperands(); i < e; i++) {
-        out.printf(" (%s)", getOperand(i)->toString());
+        fprintf(fp, " (%s)", getOperand(i)->toString());
         if (i != numOperands() - 1)
-            out.printf(",");
+            fprintf(fp, ",");
     }
 }
 
 void
 LInstruction::assignSnapshot(LSnapshot* snapshot)
 {
     MOZ_ASSERT(!snapshot_);
     snapshot_ = snapshot;
 
 #ifdef DEBUG
     if (JitSpewEnabled(JitSpew_IonSnapshots)) {
         JitSpewHeader(JitSpew_IonSnapshots);
-        Fprinter& out = JitSpewPrinter();
-        out.printf("Assigning snapshot %p to instruction %p (",
-                   (void*)snapshot, (void*)this);
-        printName(out);
-        out.printf(")\n");
+        fprintf(JitSpewFile, "Assigning snapshot %p to instruction %p (",
+                (void*)snapshot, (void*)this);
+        printName(JitSpewFile);
+        fprintf(JitSpewFile, ")\n");
     }
 #endif
 }
 
 void
-LNode::dump(GenericPrinter& out)
+LNode::dump(FILE* fp)
 {
     if (numDefs() != 0) {
-        out.printf("{");
+        fprintf(fp, "{");
         for (size_t i = 0; i < numDefs(); i++) {
-            out.printf("%s", getDef(i)->toString());
+            fprintf(fp, "%s", getDef(i)->toString());
             if (i != numDefs() - 1)
-                out.printf(", ");
+                fprintf(fp, ", ");
         }
-        out.printf("} <- ");
+        fprintf(fp, "} <- ");
     }
 
-    printName(out);
-    printOperands(out);
+    printName(fp);
+    printOperands(fp);
 
     if (numTemps()) {
-        out.printf(" t=(");
+        fprintf(fp, " t=(");
         for (size_t i = 0; i < numTemps(); i++) {
-            out.printf("%s", getTemp(i)->toString());
+            fprintf(fp, "%s", getTemp(i)->toString());
             if (i != numTemps() - 1)
-                out.printf(", ");
+                fprintf(fp, ", ");
         }
-        out.printf(")");
+        fprintf(fp, ")");
     }
 
     if (numSuccessors()) {
-        out.printf(" s=(");
+        fprintf(fp, " s=(");
         for (size_t i = 0; i < numSuccessors(); i++) {
-            out.printf("block%u", getSuccessor(i)->id());
+            fprintf(fp, "block%u", getSuccessor(i)->id());
             if (i != numSuccessors() - 1)
-                out.printf(", ");
+                fprintf(fp, ", ");
         }
-        out.printf(")");
+        fprintf(fp, ")");
     }
 }
 
 void
 LNode::dump()
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.printf("\n");
-    out.finish();
+    dump(stderr);
+    fprintf(stderr, "\n");
 }
 
 void
 LInstruction::initSafepoint(TempAllocator& alloc)
 {
     MOZ_ASSERT(!safepoint_);
     safepoint_ = new(alloc) LSafepoint(alloc);
     MOZ_ASSERT(safepoint_);
@@ -593,23 +586,23 @@ LMoveGroup::addAfter(LAllocation* from, 
             return true;
         }
     }
 
     return add(from, to, type);
 }
 
 void
-LMoveGroup::printOperands(GenericPrinter& out)
+LMoveGroup::printOperands(FILE* fp)
 {
     for (size_t i = 0; i < numMoves(); i++) {
         const LMove& move = getMove(i);
         // Use two printfs, as LAllocation::toString is not reentrant.
-        out.printf(" [%s", move.from()->toString());
-        out.printf(" -> %s", move.to()->toString());
+        fprintf(fp, " [%s", move.from()->toString());
+        fprintf(fp, " -> %s", move.to()->toString());
 #ifdef DEBUG
-        out.printf(", %s", TypeChars[move.type()]);
+        fprintf(fp, ", %s", TypeChars[move.type()]);
 #endif
-        out.printf("]");
+        fprintf(fp, "]");
         if (i != numMoves() - 1)
-            out.printf(",");
+            fprintf(fp, ",");
     }
 }
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -697,21 +697,21 @@ class LNode
     }
 
     // For an instruction which has a MUST_REUSE_INPUT output, whether that
     // output register will be restored to its original value when bailing out.
     virtual bool recoversInput() const {
         return false;
     }
 
-    virtual void dump(GenericPrinter& out);
+    virtual void dump(FILE* fp);
     void dump();
-    static void printName(GenericPrinter& out, Opcode op);
-    virtual void printName(GenericPrinter& out);
-    virtual void printOperands(GenericPrinter& out);
+    static void printName(FILE* fp, Opcode op);
+    virtual void printName(FILE* fp);
+    virtual void printOperands(FILE* fp);
 
   public:
     // Opcode testing and casts.
 #   define LIROP(name)                                                      \
     bool is##name() const {                                                 \
         return op() == LOp_##name;                                          \
     }                                                                       \
     inline L##name* to##name();                                             \
@@ -981,17 +981,17 @@ class LBlock
     LMoveGroup* getExitMoveGroup(TempAllocator& alloc);
 
     // Test whether this basic block is empty except for a simple goto, and
     // which is not forming a loop. No code will be emitted for such blocks.
     bool isTrivial() {
         return begin()->isGoto() && !mir()->isLoopHeader();
     }
 
-    void dump(GenericPrinter& out);
+    void dump(FILE* fp);
     void dump();
 };
 
 namespace details {
     template <size_t Defs, size_t Temps>
     class LInstructionFixedDefsTempsHelper : public LInstruction
     {
         mozilla::Array<LDefinition, Defs> defs_;
@@ -1794,17 +1794,17 @@ class LIRGraph
     }
     size_t numSafepoints() const {
         return safepoints_.length();
     }
     LInstruction* getSafepoint(size_t i) const {
         return safepoints_[i];
     }
 
-    void dump(GenericPrinter& out);
+    void dump(FILE* fp);
     void dump();
 };
 
 LAllocation::LAllocation(AnyRegister reg)
 {
     if (reg.isFloat())
         *this = LFloatReg(reg.fpu());
     else
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4167,38 +4167,37 @@ void
 LIRGenerator::visitNurseryObject(MNurseryObject* ins)
 {
     define(new(alloc()) LNurseryObject(), ins);
 }
 
 static void
 SpewResumePoint(MBasicBlock* block, MInstruction* ins, MResumePoint* resumePoint)
 {
-    Fprinter& out = JitSpewPrinter();
-    out.printf("Current resume point %p details:\n", (void*)resumePoint);
-    out.printf("    frame count: %u\n", resumePoint->frameCount());
+    fprintf(JitSpewFile, "Current resume point %p details:\n", (void*)resumePoint);
+    fprintf(JitSpewFile, "    frame count: %u\n", resumePoint->frameCount());
 
     if (ins) {
-        out.printf("    taken after: ");
-        ins->printName(out);
+        fprintf(JitSpewFile, "    taken after: ");
+        ins->printName(JitSpewFile);
     } else {
-        out.printf("    taken at block %d entry", block->id());
+        fprintf(JitSpewFile, "    taken at block %d entry", block->id());
     }
-    out.printf("\n");
-
-    out.printf("    pc: %p (script: %p, offset: %d)\n",
+    fprintf(JitSpewFile, "\n");
+
+    fprintf(JitSpewFile, "    pc: %p (script: %p, offset: %d)\n",
             (void*)resumePoint->pc(),
             (void*)resumePoint->block()->info().script(),
             int(resumePoint->block()->info().script()->pcToOffset(resumePoint->pc())));
 
     for (size_t i = 0, e = resumePoint->numOperands(); i < e; i++) {
         MDefinition* in = resumePoint->getOperand(i);
-        out.printf("    slot%u: ", (unsigned)i);
-        in->printName(out);
-        out.printf("\n");
+        fprintf(JitSpewFile, "    slot%u: ", (unsigned)i);
+        in->printName(JitSpewFile);
+        fprintf(JitSpewFile, "\n");
     }
 }
 
 bool
 LIRGenerator::visitInstruction(MInstruction* ins)
 {
     if (ins->isRecoveredOnBailout())
         return true;
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -258,16 +258,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
 
     // Testing Functions
     if (native == testingFunc_bailout)
         return inlineBailout(callInfo);
     if (native == testingFunc_assertFloat32)
         return inlineAssertFloat32(callInfo);
     if (native == testingFunc_assertRecoveredOnBailout)
         return inlineAssertRecoveredOnBailout(callInfo);
+    if (native == testingFunc_inIon || native == testingFunc_inJit)
+        return inlineTrue(callInfo);
 
     // Bound function
     if (native == js::CallOrConstructBoundFunction)
         return inlineBoundFunction(callInfo, target);
 
     // Simd functions
 #define INLINE_FLOAT32X4_SIMD_ARITH_(OP)                                                         \
     if (native == js::simd_float32x4_##OP)                                                       \
@@ -2666,16 +2668,25 @@ IonBuilder::inlineBailout(CallInfo& call
 
     MConstant* undefined = MConstant::New(alloc(), UndefinedValue());
     current->add(undefined);
     current->push(undefined);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineTrue(CallInfo& callInfo)
+{
+    callInfo.setImplicitlyUsedUnchecked();
+
+    pushConstant(BooleanValue(true));
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineAssertFloat32(CallInfo& callInfo)
 {
     if (callInfo.argc() != 2)
         return InliningStatus_NotInlined;
 
     MDefinition* secondArg = callInfo.getArg(1);
 
     MOZ_ASSERT(secondArg->type() == MIRType_Boolean);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -56,28 +56,28 @@ CheckUsesAreFloat32Consumers(MInstructio
 {
     bool allConsumerUses = true;
     for (MUseDefIterator use(ins); allConsumerUses && use; use++)
         allConsumerUses &= use.def()->canConsumeFloat32(use.use());
     return allConsumerUses;
 }
 
 void
-MDefinition::PrintOpcodeName(GenericPrinter& out, MDefinition::Opcode op)
+MDefinition::PrintOpcodeName(FILE* fp, MDefinition::Opcode op)
 {
     static const char * const names[] =
     {
 #define NAME(x) #x,
         MIR_OPCODE_LIST(NAME)
 #undef NAME
     };
     const char* name = names[op];
     size_t len = strlen(name);
     for (size_t i = 0; i < len; i++)
-        out.printf("%c", tolower(name[i]));
+        fprintf(fp, "%c", tolower(name[i]));
 }
 
 const Value&
 MDefinition::constantValue()
 {
     MOZ_ASSERT(isConstantValue());
 
     if (isBox())
@@ -204,20 +204,20 @@ EvaluateExactReciprocal(TempAllocator& a
     ins->block()->insertBefore(ins, foldedRhs);
 
     MMul* mul = MMul::New(alloc, left, foldedRhs, ins->type());
     mul->setCommutative();
     return mul;
 }
 
 void
-MDefinition::printName(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
-    out.printf("%u", id());
+MDefinition::printName(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
+    fprintf(fp, "%u", id());
 }
 
 HashNumber
 MDefinition::addU32ToHash(HashNumber hash, uint32_t data)
 {
     return data + (hash << 6) + (hash << 16) - hash;
 }
 
@@ -434,78 +434,74 @@ MTest::filtersUndefinedOrNull(bool trueB
         return;
     }
 
     *filtersUndefined = *filtersNull = false;
     *subject = nullptr;
 }
 
 void
-MDefinition::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
+MDefinition::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
     for (size_t j = 0, e = numOperands(); j < e; j++) {
-        out.printf(" ");
+        fprintf(fp, " ");
         if (getUseFor(j)->hasProducer())
-            getOperand(j)->printName(out);
+            getOperand(j)->printName(fp);
         else
-            out.printf("(null)");
+            fprintf(fp, "(null)");
     }
 }
 
 void
-MDefinition::dump(GenericPrinter& out) const
-{
-    printName(out);
-    out.printf(" = ");
-    printOpcode(out);
-    out.printf("\n");
+MDefinition::dump(FILE* fp) const
+{
+    printName(fp);
+    fprintf(fp, " = ");
+    printOpcode(fp);
+    fprintf(fp, "\n");
 
     if (isInstruction()) {
         if (MResumePoint* resume = toInstruction()->resumePoint())
-            resume->dump(out);
+            resume->dump(fp);
     }
 }
 
 void
 MDefinition::dump() const
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 void
-MDefinition::dumpLocation(GenericPrinter& out) const
+MDefinition::dumpLocation(FILE* fp) const
 {
     MResumePoint* rp = nullptr;
     const char* linkWord = nullptr;
     if (isInstruction() && toInstruction()->resumePoint()) {
         rp = toInstruction()->resumePoint();
         linkWord = "at";
     } else {
         rp = block()->entryResumePoint();
         linkWord = "after";
     }
 
     while (rp) {
         JSScript* script = rp->block()->info().script();
         uint32_t lineno = PCToLineNumber(rp->block()->info().script(), rp->pc());
-        out.printf("  %s %s:%d\n", linkWord, script->filename(), lineno);
+        fprintf(fp, "  %s %s:%d\n", linkWord, script->filename(), lineno);
         rp = rp->caller();
         linkWord = "in";
     }
 }
 
 void
 MDefinition::dumpLocation() const
 {
-    Fprinter out(stderr);
-    dumpLocation(out);
-    out.finish();
+    dumpLocation(stderr);
 }
 
 #ifdef DEBUG
 size_t
 MDefinition::useCount() const
 {
     size_t count = 0;
     for (MUseIterator i(uses_.begin()); i != uses_.end(); i++)
@@ -752,82 +748,82 @@ bool
 MConstant::congruentTo(const MDefinition* ins) const
 {
     if (!ins->isConstant())
         return false;
     return ins->toConstant()->value() == value();
 }
 
 void
-MConstant::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
-    out.printf(" ");
+MConstant::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
+    fprintf(fp, " ");
     switch (type()) {
       case MIRType_Undefined:
-        out.printf("undefined");
+        fprintf(fp, "undefined");
         break;
       case MIRType_Null:
-        out.printf("null");
+        fprintf(fp, "null");
         break;
       case MIRType_Boolean:
-        out.printf(value().toBoolean() ? "true" : "false");
+        fprintf(fp, value().toBoolean() ? "true" : "false");
         break;
       case MIRType_Int32:
-        out.printf("0x%x", value().toInt32());
+        fprintf(fp, "0x%x", value().toInt32());
         break;
       case MIRType_Double:
-        out.printf("%f", value().toDouble());
+        fprintf(fp, "%f", value().toDouble());
         break;
       case MIRType_Float32:
       {
         float val = value().toDouble();
-        out.printf("%f", val);
+        fprintf(fp, "%f", val);
         break;
       }
       case MIRType_Object:
         if (value().toObject().is<JSFunction>()) {
             JSFunction* fun = &value().toObject().as<JSFunction>();
             if (fun->displayAtom()) {
-                out.put("function ");
-                EscapedStringPrinter(out, fun->displayAtom(), 0);
+                fputs("function ", fp);
+                FileEscapedString(fp, fun->displayAtom(), 0);
             } else {
-                out.put("unnamed function");
+                fputs("unnamed function", fp);
             }
             if (fun->hasScript()) {
                 JSScript* script = fun->nonLazyScript();
-                out.printf(" (%s:%" PRIuSIZE ")",
+                fprintf(fp, " (%s:%" PRIuSIZE ")",
                         script->filename() ? script->filename() : "", script->lineno());
             }
-            out.printf(" at %p", (void*) fun);
+            fprintf(fp, " at %p", (void*) fun);
             break;
         }
-        out.printf("object %p (%s)", (void*)&value().toObject(),
+        fprintf(fp, "object %p (%s)", (void*)&value().toObject(),
                 value().toObject().getClass()->name);
         break;
       case MIRType_Symbol:
-        out.printf("symbol at %p", (void*)value().toSymbol());
+        fprintf(fp, "symbol at %p", (void*)value().toSymbol());
         break;
       case MIRType_String:
-        out.printf("string %p", (void*)value().toString());
+        fprintf(fp, "string %p", (void*)value().toString());
         break;
       case MIRType_MagicOptimizedArguments:
-        out.printf("magic lazyargs");
+        fprintf(fp, "magic lazyargs");
         break;
       case MIRType_MagicHole:
-        out.printf("magic hole");
+        fprintf(fp, "magic hole");
         break;
       case MIRType_MagicIsConstructing:
-        out.printf("magic is-constructing");
+        fprintf(fp, "magic is-constructing");
         break;
       case MIRType_MagicOptimizedOut:
-        out.printf("magic optimized-out");
+        fprintf(fp, "magic optimized-out");
         break;
       case MIRType_MagicUninitializedLexical:
-        out.printf("magic uninitialized-lexical");
+        fprintf(fp, "magic uninitialized-lexical");
         break;
       default:
         MOZ_CRASH("unexpected type");
     }
 }
 
 bool
 MConstant::canProduceFloat32() const
@@ -1000,96 +996,98 @@ MSimdGeneralShuffle::foldsTo(TempAllocat
         return MSimdSwizzle::New(alloc, vector(0), type(), lanes[0], lanes[1], lanes[2], lanes[3]);
 
     MOZ_ASSERT(numVectors() == 2);
     return MSimdShuffle::New(alloc, vector(0), vector(1), type(), lanes[0], lanes[1], lanes[2], lanes[3]);
 }
 
 template <typename T>
 static void
-PrintOpcodeOperation(T* mir, GenericPrinter& out)
-{
-    mir->MDefinition::printOpcode(out);
-    out.printf(" (%s)", T::OperationName(mir->operation()));
+PrintOpcodeOperation(T* mir, FILE* fp)
+{
+    mir->MDefinition::printOpcode(fp);
+    fprintf(fp, " (%s)", T::OperationName(mir->operation()));
 }
 
 void
-MSimdBinaryArith::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeOperation(this, out);
+MSimdBinaryArith::printOpcode(FILE* fp) const
+{
+    PrintOpcodeOperation(this, fp);
 }
 void
-MSimdBinaryBitwise::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeOperation(this, out);
+MSimdBinaryBitwise::printOpcode(FILE* fp) const
+{
+    PrintOpcodeOperation(this, fp);
 }
 void
-MSimdUnaryArith::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeOperation(this, out);
+MSimdUnaryArith::printOpcode(FILE* fp) const
+{
+    PrintOpcodeOperation(this, fp);
 }
 void
-MSimdBinaryComp::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeOperation(this, out);
+MSimdBinaryComp::printOpcode(FILE* fp) const
+{
+    PrintOpcodeOperation(this, fp);
 }
 void
-MSimdShift::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeOperation(this, out);
+MSimdShift::printOpcode(FILE* fp) const
+{
+    PrintOpcodeOperation(this, fp);
 }
 
 void
-MSimdInsertElement::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-    out.printf(" (%s)", MSimdInsertElement::LaneName(lane()));
+MSimdInsertElement::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+    fprintf(fp, " (%s)", MSimdInsertElement::LaneName(lane()));
 }
 
 MCloneLiteral*
 MCloneLiteral::New(TempAllocator& alloc, MDefinition* obj)
 {
     return new(alloc) MCloneLiteral(obj);
 }
 
 void
-MControlInstruction::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
+MControlInstruction::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
     for (size_t j = 0; j < numSuccessors(); j++)
-        out.printf(" block%u", getSuccessor(j)->id());
+        fprintf(fp, " block%u", getSuccessor(j)->id());
 }
 
 void
-MCompare::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-    out.printf(" %s", js_CodeName[jsop()]);
+MCompare::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+    fprintf(fp, " %s", js_CodeName[jsop()]);
 }
 
 void
-MConstantElements::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
-    out.printf(" %p", value());
+MConstantElements::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
+    fprintf(fp, " %p", value());
 }
 
 void
-MLoadUnboxedScalar::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-    out.printf(" %s", ScalarTypeDescr::typeName(indexType()));
+MLoadUnboxedScalar::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+    fprintf(fp, " %s", ScalarTypeDescr::typeName(indexType()));
 }
 
 void
-MAssertRange::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-    out.put(" ");
-    assertedRange()->dump(out);
+MAssertRange::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+    Sprinter sp(GetJitContext()->cx);
+    sp.init();
+    assertedRange()->print(sp);
+    fprintf(fp, " %s", sp.string());
 }
 
 const char*
 MMathFunction::FunctionName(Function function)
 {
     switch (function) {
       case Log:    return "Log";
       case Sin:    return "Sin";
@@ -1116,20 +1114,20 @@ MMathFunction::FunctionName(Function fun
       case Ceil:   return "Ceil";
       case Round:  return "Round";
       default:
         MOZ_CRASH("Unknown math function");
     }
 }
 
 void
-MMathFunction::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-    out.printf(" %s", FunctionName(function()));
+MMathFunction::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+    fprintf(fp, " %s", FunctionName(function()));
 }
 
 MDefinition*
 MMathFunction::foldsTo(TempAllocator& alloc)
 {
     MDefinition* input = getOperand(0);
     if (!input->isConstant())
         return this;
@@ -1224,23 +1222,23 @@ MMathFunction::foldsTo(TempAllocator& al
 
 MParameter*
 MParameter::New(TempAllocator& alloc, int32_t index, TemporaryTypeSet* types)
 {
     return new(alloc) MParameter(index, types);
 }
 
 void
-MParameter::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
+MParameter::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
     if (index() == THIS_SLOT)
-        out.printf(" THIS_SLOT");
+        fprintf(fp, " THIS_SLOT");
     else
-        out.printf(" %d", index());
+        fprintf(fp, " %d", index());
 }
 
 HashNumber
 MParameter::valueHash() const
 {
     HashNumber hash = MDefinition::valueHash();
     hash = addU32ToHash(hash, index_);
     return hash;
@@ -1482,47 +1480,47 @@ MTableSwitch::New(TempAllocator& alloc, 
 MGoto*
 MGoto::New(TempAllocator& alloc, MBasicBlock* target)
 {
     MOZ_ASSERT(target);
     return new(alloc) MGoto(target);
 }
 
 void
-MUnbox::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
-    out.printf(" ");
-    getOperand(0)->printName(out);
-    out.printf(" ");
+MUnbox::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
+    fprintf(fp, " ");
+    getOperand(0)->printName(fp);
+    fprintf(fp, " ");
 
     switch (type()) {
-      case MIRType_Int32: out.printf("to Int32"); break;
-      case MIRType_Double: out.printf("to Double"); break;
-      case MIRType_Boolean: out.printf("to Boolean"); break;
-      case MIRType_String: out.printf("to String"); break;
-      case MIRType_Symbol: out.printf("to Symbol"); break;
-      case MIRType_Object: out.printf("to Object"); break;
+      case MIRType_Int32: fprintf(fp, "to Int32"); break;
+      case MIRType_Double: fprintf(fp, "to Double"); break;
+      case MIRType_Boolean: fprintf(fp, "to Boolean"); break;
+      case MIRType_String: fprintf(fp, "to String"); break;
+      case MIRType_Symbol: fprintf(fp, "to Symbol"); break;
+      case MIRType_Object: fprintf(fp, "to Object"); break;
       default: break;
     }
 
     switch (mode()) {
-      case Fallible: out.printf(" (fallible)"); break;
-      case Infallible: out.printf(" (infallible)"); break;
-      case TypeBarrier: out.printf(" (typebarrier)"); break;
+      case Fallible: fprintf(fp, " (fallible)"); break;
+      case Infallible: fprintf(fp, " (infallible)"); break;
+      case TypeBarrier: fprintf(fp, " (typebarrier)"); break;
       default: break;
     }
 }
 
 void
-MTypeBarrier::printOpcode(GenericPrinter& out) const
-{
-    PrintOpcodeName(out, op());
-    out.printf(" ");
-    getOperand(0)->printName(out);
+MTypeBarrier::printOpcode(FILE* fp) const
+{
+    PrintOpcodeName(fp, op());
+    fprintf(fp, " ");
+    getOperand(0)->printName(fp);
 }
 
 bool
 MTypeBarrier::congruentTo(const MDefinition* def) const
 {
     if (!def->isTypeBarrier())
         return false;
     const MTypeBarrier* other = def->toTypeBarrier();
@@ -3222,51 +3220,49 @@ MResumePoint::addStore(TempAllocator& al
     // Ensure that the store would not be deleted by DCE.
     MOZ_ASSERT(store->isEffectful());
 
     MStoreToRecover* top = new(alloc) MStoreToRecover(store);
     stores_.push(top);
 }
 
 void
-MResumePoint::dump(GenericPrinter& out) const
-{
-    out.printf("resumepoint mode=");
+MResumePoint::dump(FILE* fp) const
+{
+    fprintf(fp, "resumepoint mode=");
 
     switch (mode()) {
       case MResumePoint::ResumeAt:
-        out.printf("At");
+        fprintf(fp, "At");
         break;
       case MResumePoint::ResumeAfter:
-        out.printf("After");
+        fprintf(fp, "After");
         break;
       case MResumePoint::Outer:
-        out.printf("Outer");
+        fprintf(fp, "Outer");
         break;
     }
 
     if (MResumePoint* c = caller())
-        out.printf(" (caller in block%u)", c->block()->id());
+        fprintf(fp, " (caller in block%u)", c->block()->id());
 
     for (size_t i = 0; i < numOperands(); i++) {
-        out.printf(" ");
+        fprintf(fp, " ");
         if (operands_[i].hasProducer())
-            getOperand(i)->printName(out);
+            getOperand(i)->printName(fp);
         else
-            out.printf("(null)");
+            fprintf(fp, "(null)");
     }
-    out.printf("\n");
+    fprintf(fp, "\n");
 }
 
 void
 MResumePoint::dump() const
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 bool
 MResumePoint::isObservableOperand(MUse* u) const
 {
     return isObservableOperand(indexOf(u));
 }
 
@@ -3899,22 +3895,28 @@ void
 MNot::trySpecializeFloat32(TempAllocator& alloc)
 {
     MDefinition* in = input();
     if (!in->canProduceFloat32() && in->type() == MIRType_Float32)
         ConvertDefinitionToDouble<0>(alloc, in, this);
 }
 
 void
-MBeta::printOpcode(GenericPrinter& out) const
-{
-    MDefinition::printOpcode(out);
-
-    out.printf(" ");
-    comparison_->dump(out);
+MBeta::printOpcode(FILE* fp) const
+{
+    MDefinition::printOpcode(fp);
+
+    if (JitContext* context = MaybeGetJitContext()) {
+        Sprinter sp(context->cx);
+        sp.init();
+        comparison_->print(sp);
+        fprintf(fp, " %s", sp.string());
+    } else {
+        fprintf(fp, " ???");
+    }
 }
 
 bool
 MNewObject::shouldUseVM() const
 {
     if (JSObject* obj = templateObject())
         return obj->is<PlainObject>() && obj->as<PlainObject>().hasDynamicSlots();
     return true;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -276,17 +276,17 @@ class MNode : public TempObject
         return getUseFor(index)->hasProducer();
     }
 
     inline MDefinition* toDefinition();
     inline MResumePoint* toResumePoint();
 
     virtual bool writeRecoverData(CompactBufferWriter& writer) const;
 
-    virtual void dump(GenericPrinter& out) const = 0;
+    virtual void dump(FILE* fp) const = 0;
     virtual void dump() const = 0;
 
   protected:
     // Need visibility on getUseFor to avoid O(n^2) complexity.
     friend void AssertBasicGraphCoherency(MIRGraph& graph);
 
     // Gets the MUse corresponding to given operand.
     virtual MUse* getUseFor(size_t index) = 0;
@@ -440,22 +440,22 @@ class MDefinition : public MNode
         dependency_(other.dependency_),
         trackedSite_(other.trackedSite_)
     { }
 
     virtual Opcode op() const = 0;
     virtual const char* opName() const = 0;
     virtual void accept(MDefinitionVisitor* visitor) = 0;
 
-    void printName(GenericPrinter& out) const;
-    static void PrintOpcodeName(GenericPrinter& out, Opcode op);
-    virtual void printOpcode(GenericPrinter& out) const;
-    void dump(GenericPrinter& out) const override;
+    void printName(FILE* fp) const;
+    static void PrintOpcodeName(FILE* fp, Opcode op);
+    virtual void printOpcode(FILE* fp) const;
+    void dump(FILE* fp) const override;
     void dump() const override;
-    void dumpLocation(GenericPrinter& out) const;
+    void dumpLocation(FILE* fp) const;
     void dumpLocation() const;
 
     // For LICM.
     virtual bool neverHoist() const { return false; }
 
     // Also for LICM. Test whether this definition is likely to be a call, which
     // would clobber all or many of the floating-point registers, such that
     // hoisting floating-point constants out of containing loops isn't likely to
@@ -748,17 +748,19 @@ class MDefinition : public MNode
     // returning false if the replacement should not be performed. For use when
     // GVN eliminates instructions which are not equivalent to one another.
     virtual bool updateForReplacement(MDefinition* ins) {
         return true;
     }
 
     void setVirtualRegister(uint32_t vreg) {
         virtualRegister_ = vreg;
+#ifdef DEBUG
         setLoweredUnchecked();
+#endif
     }
     uint32_t virtualRegister() const {
         MOZ_ASSERT(isLowered());
         return virtualRegister_;
     }
 
   public:
     // Opcode testing and casts.
@@ -1324,17 +1326,17 @@ class MConstant : public MNullaryInstruc
     const js::Value* vp() const {
         return &value_;
     }
     bool valueToBoolean() const {
         // A hack to avoid this wordy pattern everywhere in the JIT.
         return ToBoolean(HandleValue::fromMarkedLocation(&value_));
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     HashNumber valueHash() const override;
     bool congruentTo(const MDefinition* ins) const override;
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
@@ -1715,17 +1717,17 @@ class MSimdInsertElement
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
     bool congruentTo(const MDefinition* ins) const override {
         return binaryCongruentTo(ins) && lane_ == ins->toSimdInsertElement()->lane();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     ALLOW_CLONE(MSimdInsertElement)
 };
 
 // Extracts the sign bits from a given vector, returning an MIRType_Int32.
 class MSimdSignMask
   : public MUnaryInstruction,
     public SimdPolicy<0>::Data
@@ -2041,17 +2043,17 @@ class MSimdUnaryArith
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
     bool congruentTo(const MDefinition* ins) const override {
         return congruentIfOperandsEqual(ins) && ins->toSimdUnaryArith()->operation() == operation();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     ALLOW_CLONE(MSimdUnaryArith);
 };
 
 // Compares each value of a SIMD vector to each corresponding lane's value of
 // another SIMD vector, and returns a int32x4 vector containing the results of
 // the comparison: all bits are set to 1 if the comparison is true, 0 otherwise.
 class MSimdBinaryComp
@@ -2128,17 +2130,17 @@ class MSimdBinaryComp
     bool congruentTo(const MDefinition* ins) const override {
         if (!binaryCongruentTo(ins))
             return false;
         const MSimdBinaryComp* other = ins->toSimdBinaryComp();
         return specialization_ == other->specialization() &&
                operation_ == other->operation();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     ALLOW_CLONE(MSimdBinaryComp)
 };
 
 class MSimdBinaryArith
   : public MBinaryInstruction,
     public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdSameAsReturnedTypePolicy<1> >::Data
 {
@@ -2197,17 +2199,17 @@ class MSimdBinaryArith
     Operation operation() const { return operation_; }
 
     bool congruentTo(const MDefinition* ins) const override {
         if (!binaryCongruentTo(ins))
             return false;
         return operation_ == ins->toSimdBinaryArith()->operation();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     ALLOW_CLONE(MSimdBinaryArith)
 };
 
 class MSimdBinaryBitwise
   : public MBinaryInstruction,
     public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdSameAsReturnedTypePolicy<1> >::Data
 {
@@ -2262,17 +2264,17 @@ class MSimdBinaryBitwise
     Operation operation() const { return operation_; }
 
     bool congruentTo(const MDefinition* ins) const override {
         if (!binaryCongruentTo(ins))
             return false;
         return operation_ == ins->toSimdBinaryBitwise()->operation();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     ALLOW_CLONE(MSimdBinaryBitwise)
 };
 
 class MSimdShift
   : public MBinaryInstruction,
     public MixPolicy<SimdSameAsReturnedTypePolicy<0>, SimdScalarPolicy<1> >::Data
 {
@@ -2319,17 +2321,17 @@ class MSimdShift
         switch (op) {
           case lsh:  return "lsh";
           case rsh:  return "rsh-arithmetic";
           case ursh: return "rhs-logical";
         }
         MOZ_CRASH("unexpected operation");
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     bool congruentTo(const MDefinition* ins) const override {
         if (!binaryCongruentTo(ins))
             return false;
         return operation_ == ins->toSimdShift()->operation();
     }
 
     ALLOW_CLONE(MSimdShift)
@@ -2422,17 +2424,17 @@ class MParameter : public MNullaryInstru
 
   public:
     INSTRUCTION_HEADER(Parameter)
     static MParameter* New(TempAllocator& alloc, int32_t index, TemporaryTypeSet* types);
 
     int32_t index() const {
         return index_;
     }
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     HashNumber valueHash() const override;
     bool congruentTo(const MDefinition* ins) const override;
 };
 
 class MCallee : public MNullaryInstruction
 {
   public:
@@ -2488,17 +2490,17 @@ class MControlInstruction : public MInst
     virtual size_t numSuccessors() const = 0;
     virtual MBasicBlock* getSuccessor(size_t i) const = 0;
     virtual void replaceSuccessor(size_t i, MBasicBlock* successor) = 0;
 
     bool isControlInstruction() const override {
         return true;
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 };
 
 class MTableSwitch final
   : public MControlInstruction,
     public NoFloatPolicy<0>::Data
 {
     // The successors of the tableswitch
     // - First successor = the default case
@@ -4258,17 +4260,17 @@ class MCompare
         if (jsop_ == JSOP_STRICTEQ || jsop_ == JSOP_STRICTNE)
             return AliasSet::None();
         if (compareType_ == Compare_Unknown)
             return AliasSet::Store(AliasSet::Any);
         MOZ_ASSERT(compareType_ <= Compare_Value);
         return AliasSet::None();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
     void collectRangeInfoPreTrunc() override;
 
     void trySpecializeFloat32(TempAllocator& alloc) override;
     bool isFloat32Commutative() const override { return true; }
     bool needTruncation(TruncateKind kind) override;
     void truncate() override;
     TruncateKind operandTruncateKind(size_t index) const override;
 
@@ -4437,17 +4439,17 @@ class MUnbox final : public MUnaryInstru
     bool congruentTo(const MDefinition* ins) const override {
         if (!ins->isUnbox() || ins->toUnbox()->mode() != mode())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
     void makeInfallible() {
         // Should only be called if we're already Infallible or TypeBarrier
         MOZ_ASSERT(mode() != Fallible);
         mode_ = Infallible;
     }
 
     ALLOW_CLONE(MUnbox)
 };
@@ -4549,17 +4551,17 @@ class MAssertRange
     const Range* assertedRange() const {
         return assertedRange_;
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 };
 
 // Caller-side allocation of |this| for |new|:
 // Given a templateobject, construct |this| for JSOP_NEW
 class MCreateThisWithTemplate
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
@@ -6083,17 +6085,17 @@ class MMathFunction
     }
 
     bool possiblyCalls() const override {
         return true;
     }
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     static const char* FunctionName(Function function);
 
     bool isFloat32Commutative() const override {
         return function_ == Floor || function_ == Ceil || function_ == Round;
     }
     void trySpecializeFloat32(TempAllocator& alloc) override;
     void computeRange(TempAllocator& alloc) override;
@@ -6903,17 +6905,17 @@ class MBeta
           comparison_(comp)
     {
         setResultType(val->type());
         setResultTypeSet(val->resultTypeSet());
     }
 
   public:
     INSTRUCTION_HEADER(Beta)
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
     static MBeta* New(TempAllocator& alloc, MDefinition* val, const Range* comp)
     {
         return new(alloc) MBeta(val, comp);
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
@@ -7654,17 +7656,17 @@ class MConstantElements : public MNullar
     static MConstantElements* New(TempAllocator& alloc, void* v) {
         return new(alloc) MConstantElements(v);
     }
 
     void* value() const {
         return value_;
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     HashNumber valueHash() const override {
         return (HashNumber)(size_t) value_;
     }
 
     bool congruentTo(const MDefinition* ins) const override {
         return ins->isConstantElements() && ins->toConstantElements()->value() == value();
     }
@@ -9235,17 +9237,17 @@ class MLoadUnboxedScalar
             return false;
         if (offsetAdjustment() != other->offsetAdjustment())
             return false;
         if (canonicalizeDoubles() != other->canonicalizeDoubles())
             return false;
         return congruentIfOperandsEqual(other);
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
 
     void computeRange(TempAllocator& alloc) override;
 
     bool canProduceFloat32() const override { return indexType_ == Scalar::Float32; }
 
     ALLOW_CLONE(MLoadUnboxedScalar)
 };
 
@@ -12050,17 +12052,17 @@ class MTypeBarrier
   public:
     INSTRUCTION_HEADER(TypeBarrier)
 
     static MTypeBarrier* New(TempAllocator& alloc, MDefinition* def, TemporaryTypeSet* types,
                              BarrierKind kind = BarrierKind::TypeSet) {
         return new(alloc) MTypeBarrier(def, types, kind);
     }
 
-    void printOpcode(GenericPrinter& out) const override;
+    void printOpcode(FILE* fp) const override;
     bool congruentTo(const MDefinition* def) const override;
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
     virtual bool neverHoist() const override {
         return resultTypeSet()->empty();
     }
@@ -12436,17 +12438,17 @@ class MResumePoint final :
 
     MStoresToRecoverList::iterator storesBegin() const {
         return stores_.begin();
     }
     MStoresToRecoverList::iterator storesEnd() const {
         return stores_.end();
     }
 
-    virtual void dump(GenericPrinter& out) const override;
+    virtual void dump(FILE* fp) const override;
     virtual void dump() const override;
 };
 
 class MIsCallable
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
     explicit MIsCallable(MDefinition* object)
--- a/js/src/jit/MIRGenerator.h
+++ b/js/src/jit/MIRGenerator.h
@@ -240,22 +240,14 @@ class MIRGenerator
         return conversionErrorLabel_;
     }
     Label* outOfBoundsLabel() const {
         MOZ_ASSERT(compilingAsmJS());
         return outOfBoundsLabel_;
     }
     bool needsAsmJSBoundsCheckBranch(const MAsmJSHeapAccess* access) const;
     size_t foldableOffsetRange(const MAsmJSHeapAccess* access) const;
-
-  private:
-    GraphSpewer gs_;
-
-  public:
-    GraphSpewer& graphSpewer() {
-        return gs_;
-    }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_MIRGenerator_h */
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -43,18 +43,17 @@ MIRGenerator::MIRGenerator(CompileCompar
     instrumentedProfiling_(false),
     instrumentedProfilingIsCached_(false),
     nurseryObjects_(*alloc),
     outOfBoundsLabel_(outOfBoundsLabel),
     conversionErrorLabel_(conversionErrorLabel),
 #if defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB)
     usesSignalHandlersForAsmJSOOB_(usesSignalHandlersForAsmJSOOB),
 #endif
-    options(options),
-    gs_(alloc)
+    options(options)
 { }
 
 bool
 MIRGenerator::usesSimd()
 {
     if (usesSimdCached_)
         return usesSimd_;
 
@@ -1512,16 +1511,29 @@ MBasicBlock::specializePhis()
     for (MPhiIterator iter = phisBegin(); iter != phisEnd(); iter++) {
         MPhi* phi = *iter;
         if (!phi->specializeType())
             return false;
     }
     return true;
 }
 
+void
+MBasicBlock::dumpStack(FILE* fp)
+{
+#ifdef DEBUG
+    fprintf(fp, " %-3s %-16s %-6s %-10s\n", "#", "name", "copyOf", "first/next");
+    fprintf(fp, "-------------------------------------------\n");
+    for (uint32_t i = 0; i < stackPosition_; i++) {
+        fprintf(fp, " %-3d", i);
+        fprintf(fp, " %-16p\n", (void*)slots_[i]);
+    }
+#endif
+}
+
 MTest*
 MBasicBlock::immediateDominatorBranch(BranchDirection* pdirection)
 {
     *pdirection = FALSE_BRANCH;
 
     if (numPredecessors() != 1)
         return nullptr;
 
@@ -1541,71 +1553,49 @@ MBasicBlock::immediateDominatorBranch(Br
         *pdirection = (test->ifTrue() == this) ? TRUE_BRANCH : FALSE_BRANCH;
         return test;
     }
 
     return nullptr;
 }
 
 void
-MBasicBlock::dumpStack(GenericPrinter& out)
-{
-#ifdef DEBUG
-    out.printf(" %-3s %-16s %-6s %-10s\n", "#", "name", "copyOf", "first/next");
-    out.printf("-------------------------------------------\n");
-    for (uint32_t i = 0; i < stackPosition_; i++) {
-        out.printf(" %-3d", i);
-        out.printf(" %-16p\n", (void*)slots_[i]);
-    }
-#endif
-}
-
-void
-MBasicBlock::dumpStack()
-{
-    Fprinter out(stderr);
-    dumpStack(out);
-    out.finish();
-}
-
-void
-MIRGraph::dump(GenericPrinter& out)
+MIRGraph::dump(FILE* fp)
 {
 #ifdef DEBUG
     for (MBasicBlockIterator iter(begin()); iter != end(); iter++) {
-        iter->dump(out);
-        out.printf("\n");
+        iter->dump(fp);
+        fprintf(fp, "\n");
     }
 #endif
 }
 
 void
 MIRGraph::dump()
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
 
 void
-MBasicBlock::dump(GenericPrinter& out)
+MBasicBlock::dump(FILE* fp)
 {
 #ifdef DEBUG
-    out.printf("block%u:%s%s%s\n", id(),
-               isLoopHeader() ? " (loop header)" : "",
-               unreachable() ? " (unreachable)" : "",
-               isMarked() ? " (marked)" : "");
-    if (MResumePoint* resume = entryResumePoint())
-        resume->dump(out);
-    for (MPhiIterator iter(phisBegin()); iter != phisEnd(); iter++)
-        iter->dump(out);
-    for (MInstructionIterator iter(begin()); iter != end(); iter++)
-        iter->dump(out);
+    fprintf(fp, "block%u:%s%s%s\n", id(),
+            isLoopHeader() ? " (loop header)" : "",
+            unreachable() ? " (unreachable)" : "",
+            isMarked() ? " (marked)" : "");
+    if (MResumePoint* resume = entryResumePoint()) {
+        resume->dump();
+    }
+    for (MPhiIterator iter(phisBegin()); iter != phisEnd(); iter++) {
+        iter->dump(fp);
+    }
+    for (MInstructionIterator iter(begin()); iter != end(); iter++) {
+        iter->dump(fp);
+    }
 #endif
 }
 
 void
 MBasicBlock::dump()
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.finish();
+    dump(stderr);
 }
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -594,20 +594,19 @@ class MBasicBlock : public TempObject, p
     uint32_t loopDepth() const {
         return loopDepth_;
     }
 
     bool strict() const {
         return info_.script()->strict();
     }
 
-    void dumpStack(GenericPrinter& out);
-    void dumpStack();
+    void dumpStack(FILE* fp);
 
-    void dump(GenericPrinter& out);
+    void dump(FILE* fp);
     void dump();
 
     // Track bailouts by storing the current pc in MIR instruction added at
     // this cycle. This is also used for tracking calls and optimizations when
     // profiling.
     void updateTrackedSite(BytecodeSite* site) {
         MOZ_ASSERT(site->tree() == trackedSite_->tree());
         trackedSite_ = site;
@@ -805,17 +804,17 @@ class MIRGraph
 
     bool hasTryBlock() const {
         return hasTryBlock_;
     }
     void setHasTryBlock() {
         hasTryBlock_ = true;
     }
 
-    void dump(GenericPrinter& out);
+    void dump(FILE* fp);
     void dump();
 };
 
 class MDefinitionIterator
 {
   friend class MBasicBlock;
   friend class MNodeIterator;
 
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -110,20 +110,19 @@ IsDominatedUse(MBasicBlock* block, MUse*
 }
 
 static inline void
 SpewRange(MDefinition* def)
 {
 #ifdef DEBUG
     if (JitSpewEnabled(JitSpew_Range) && def->type() != MIRType_None && def->range()) {
         JitSpewHeader(JitSpew_Range);
-        Fprinter& out = JitSpewPrinter();
-        def->printName(out);
-        out.printf(" has range ");
-        def->range()->dump(out);
+        def->printName(JitSpewFile);
+        fprintf(JitSpewFile, " has range ");
+        def->range()->dump(JitSpewFile);
     }
 #endif
 }
 
 TempAllocator&
 RangeAnalysis::alloc() const
 {
     return graph_.alloc();
@@ -277,19 +276,18 @@ RangeAnalysis::addBetaNodes()
             continue; // well, we could have
                       // [-\inf, bound-1] U [bound+1, \inf] but we only use contiguous ranges.
           default:
             continue;
         }
 
         if (JitSpewEnabled(JitSpew_Range)) {
             JitSpewHeader(JitSpew_Range);
-            Fprinter& out = JitSpewPrinter();
-            out.printf("Adding beta node for %d with range ", val->id());
-            comp.dump(out);
+            fprintf(JitSpewFile, "Adding beta node for %d with range ", val->id());
+            comp.dump(JitSpewFile);
         }
 
         MBeta* beta = MBeta::New(alloc(), val, new(alloc()) Range(comp));
         block->insertBefore(*block->begin(), beta);
         replaceDominatedUsesWith(val, beta, block);
     }
 
     return true;
@@ -317,30 +315,30 @@ RangeAnalysis::removeBetaNodes()
                 break;
             }
         }
     }
     return true;
 }
 
 void
-SymbolicBound::dump(GenericPrinter& out) const
+SymbolicBound::print(Sprinter& sp) const
 {
     if (loop)
-        out.printf("[loop] ");
-    sum.dump(out);
+        sp.printf("[loop] ");
+    sum.print(sp);
 }
 
 void
 SymbolicBound::dump() const
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.printf("\n");
-    out.finish();
+    Sprinter sp(GetJitContext()->cx);
+    sp.init();
+    print(sp);
+    fprintf(stderr, "%s\n", sp.string());
 }
 
 // Test whether the given range's exponent tells us anything that its lower
 // and upper bound values don't.
 static bool
 IsExponentInteresting(const Range* r)
 {
    // If it lacks either a lower or upper bound, the exponent is interesting.
@@ -353,105 +351,111 @@ IsExponentInteresting(const Range* r)
        return false;
 
    // Otherwise, if the bounds are conservatively rounded across a power-of-two
    // boundary, the exponent may imply a tighter range.
    return FloorLog2(Max(Abs(r->lower()), Abs(r->upper()))) > r->exponent();
 }
 
 void
-Range::dump(GenericPrinter& out) const
+Range::print(Sprinter& sp) const
 {
     assertInvariants();
 
     // Floating-point or Integer subset.
     if (canHaveFractionalPart_)
-        out.printf("F");
+        sp.printf("F");
     else
-        out.printf("I");
-
-    out.printf("[");
+        sp.printf("I");
+
+    sp.printf("[");
 
     if (!hasInt32LowerBound_)
-        out.printf("?");
+        sp.printf("?");
     else
-        out.printf("%d", lower_);
+        sp.printf("%d", lower_);
     if (symbolicLower_) {
-        out.printf(" {");
-        symbolicLower_->dump(out);
-        out.printf("}");
+        sp.printf(" {");
+        symbolicLower_->print(sp);
+        sp.printf("}");
     }
 
-    out.printf(", ");
+    sp.printf(", ");
 
     if (!hasInt32UpperBound_)
-        out.printf("?");
+        sp.printf("?");
     else
-        out.printf("%d", upper_);
+        sp.printf("%d", upper_);
     if (symbolicUpper_) {
-        out.printf(" {");
-        symbolicUpper_->dump(out);
-        out.printf("}");
+        sp.printf(" {");
+        symbolicUpper_->print(sp);
+        sp.printf("}");
     }
 
-    out.printf("]");
+    sp.printf("]");
 
     bool includesNaN = max_exponent_ == IncludesInfinityAndNaN;
     bool includesNegativeInfinity = max_exponent_ >= IncludesInfinity && !hasInt32LowerBound_;
     bool includesPositiveInfinity = max_exponent_ >= IncludesInfinity && !hasInt32UpperBound_;
     bool includesNegativeZero = canBeNegativeZero_;
 
     if (includesNaN ||
         includesNegativeInfinity ||
         includesPositiveInfinity ||
         includesNegativeZero)
     {
-        out.printf(" (");
+        sp.printf(" (");
         bool first = true;
         if (includesNaN) {
             if (first)
                 first = false;
             else
-                out.printf(" ");
-            out.printf("U NaN");
+                sp.printf(" ");
+            sp.printf("U NaN");
         }
         if (includesNegativeInfinity) {
             if (first)
                 first = false;
             else
-                out.printf(" ");
-            out.printf("U -Infinity");
+                sp.printf(" ");
+            sp.printf("U -Infinity");
         }
         if (includesPositiveInfinity) {
             if (first)
                 first = false;
             else
-                out.printf(" ");
-            out.printf("U Infinity");
+                sp.printf(" ");
+            sp.printf("U Infinity");
         }
         if (includesNegativeZero) {
             if (first)
                 first = false;
             else
-                out.printf(" ");
-            out.printf("U -0");
+                sp.printf(" ");
+            sp.printf("U -0");
         }
-        out.printf(")");
+        sp.printf(")");
     }
     if (max_exponent_ < IncludesInfinity && IsExponentInteresting(this))
-        out.printf(" (< pow(2, %d+1))", max_exponent_);
+        sp.printf(" (< pow(2, %d+1))", max_exponent_);
+}
+
+void
+Range::dump(FILE* fp) const
+{
+    Sprinter sp(GetJitContext()->cx);
+    sp.init();
+    print(sp);
+    fprintf(fp, "%s\n", sp.string());
 }
 
 void
 Range::dump() const
 {
-    Fprinter out(stderr);
-    dump(out);
-    out.printf("\n");
-    out.finish();
+    dump(stderr);
 }
 
 Range*
 Range::intersect(TempAllocator& alloc, const Range* lhs, const Range* rhs, bool* emptyRange)
 {
     *emptyRange = false;
 
     if (!lhs && !rhs)
@@ -1862,17 +1866,17 @@ RangeAnalysis::analyzeLoop(MBasicBlock* 
 
     if (!loopIterationBounds.append(iterationBound))
         return false;
 
 #ifdef DEBUG
     if (JitSpewEnabled(JitSpew_Range)) {
         Sprinter sp(GetJitContext()->cx);
         sp.init();
-        iterationBound->boundSum.dump(sp);
+        iterationBound->boundSum.print(sp);
         JitSpew(JitSpew_Range, "computed symbolic bound on backedges: %s",
                 sp.string());
     }
 #endif
 
     // Try to compute symbolic bounds for the phi nodes at the head of this
     // loop, expressed in terms of the iteration bound just computed.
 
--- a/js/src/jit/RangeAnalysis.h
+++ b/js/src/jit/RangeAnalysis.h
@@ -76,17 +76,17 @@ struct SymbolicBound : public TempObject
 
     static SymbolicBound* New(TempAllocator& alloc, LoopIterationBound* loop, LinearSum sum) {
         return new(alloc) SymbolicBound(loop, sum);
     }
 
     // Computed symbolic bound, see above.
     LinearSum sum;
 
-    void dump(GenericPrinter& out) const;
+    void print(Sprinter& sp) const;
     void dump() const;
 };
 
 class RangeAnalysis
 {
   protected:
     bool blockDominates(MBasicBlock* b, MBasicBlock* b2);
     void replaceDominatedUsesWith(MDefinition* orig, MDefinition* dom,
@@ -445,17 +445,18 @@ class Range : public TempObject {
         if (mozilla::IsNaN(d))
             return nullptr;
 
         Range* r = new(alloc) Range();
         r->setDoubleSingleton(d);
         return r;
     }
 
-    void dump(GenericPrinter& out) const;
+    void print(Sprinter& sp) const;
+    void dump(FILE* fp) const;
     void dump() const;
     bool update(const Range* other);
 
     // Unlike the other operations, unionWith is an in-place
     // modification. This is to avoid a bunch of useless extra
     // copying when chaining together unions when handling Phi
     // nodes.
     void unionWith(const Range* other);
--- a/js/src/jit/Safepoints.cpp
+++ b/js/src/jit/Safepoints.cpp
@@ -200,23 +200,22 @@ SafepointWriter::writeValueSlots(LSafepo
 
     MapSlotsToBitset(frameSlots_, argumentSlots_, stream_, slots);
 }
 
 #if defined(DEBUG) && defined(JS_NUNBOX32)
 static void
 DumpNunboxPart(const LAllocation& a)
 {
-    Fprinter& out = JitSpewPrinter();
     if (a.isStackSlot()) {
-        out.printf("stack %d", a.toStackSlot()->slot());
+        fprintf(JitSpewFile, "stack %d", a.toStackSlot()->slot());
     } else if (a.isArgument()) {
-        out.printf("arg %d", a.toArgument()->index());
+        fprintf(JitSpewFile, "arg %d", a.toArgument()->index());
     } else {
-        out.printf("reg %s", a.toGeneralReg()->reg().name());
+        fprintf(JitSpewFile, "reg %s", a.toGeneralReg()->reg().name());
     }
 }
 #endif // DEBUG
 
 // Nunbox part encoding:
 //
 // Reg = 000
 // Stack = 001
@@ -291,22 +290,21 @@ SafepointWriter::writeNunboxParts(LSafep
 
 # ifdef DEBUG
     if (JitSpewEnabled(JitSpew_Safepoints)) {
         for (uint32_t i = 0; i < entries.length(); i++) {
             SafepointNunboxEntry& entry = entries[i];
             if (entry.type.isUse() || entry.payload.isUse())
                 continue;
             JitSpewHeader(JitSpew_Safepoints);
-            Fprinter& out = JitSpewPrinter();
-            out.printf("    nunbox (type in ");
+            fprintf(JitSpewFile, "    nunbox (type in ");
             DumpNunboxPart(entry.type);
-            out.printf(", payload in ");
+            fprintf(JitSpewFile, ", payload in ");
             DumpNunboxPart(entry.payload);
-            out.printf(")\n");
+            fprintf(JitSpewFile, ")\n");
         }
     }
 # endif
 
     // Safepoints are permitted to have partially filled in entries for nunboxes,
     // provided that only the type is live and not the payload. Omit these from
     // the written safepoint.
 
--- a/js/src/jit/Snapshots.cpp
+++ b/js/src/jit/Snapshots.cpp
@@ -11,18 +11,16 @@
 #include "jit/CompileInfo.h"
 #include "jit/JitSpewer.h"
 #ifdef TRACK_SNAPSHOTS
 # include "jit/LIR.h"
 #endif
 #include "jit/MIR.h"
 #include "jit/Recover.h"
 
-#include "vm/Printer.h"
-
 using namespace js;
 using namespace js::jit;
 
 // Encodings:
 //   [ptr] A fixed-size pointer.
 //   [vwu] A variable-width unsigned integer.
 //   [vws] A variable-width signed integer.
 //    [u8] An 8-bit unsigned integer.
@@ -416,53 +414,53 @@ ValTypeToString(JSValueType type)
       case JSVAL_TYPE_MAGIC:
         return "magic";
       default:
         MOZ_CRASH("no payload");
     }
 }
 
 void
-RValueAllocation::dumpPayload(GenericPrinter& out, PayloadType type, Payload p)
+RValueAllocation::dumpPayload(FILE* fp, PayloadType type, Payload p)
 {
     switch (type) {
       case PAYLOAD_NONE:
         break;
       case PAYLOAD_INDEX:
-        out.printf("index %u", p.index);
+        fprintf(fp, "index %u", p.index);
         break;
       case PAYLOAD_STACK_OFFSET:
-        out.printf("stack %d", p.stackOffset);
+        fprintf(fp, "stack %d", p.stackOffset);
         break;
       case PAYLOAD_GPR:
-        out.printf("reg %s", p.gpr.name());
+        fprintf(fp, "reg %s", p.gpr.name());
         break;
       case PAYLOAD_FPU:
-        out.printf("reg %s", p.fpu.name());
+        fprintf(fp, "reg %s", p.fpu.name());
         break;
       case PAYLOAD_PACKED_TAG:
-        out.printf("%s", ValTypeToString(p.type));
+        fprintf(fp, "%s", ValTypeToString(p.type));
         break;
     }
 }
 
 void
-RValueAllocation::dump(GenericPrinter& out) const
+RValueAllocation::dump(FILE* fp) const
 {
     const Layout& layout = layoutFromMode(mode());
-    out.printf("%s", layout.name);
+    fprintf(fp, "%s", layout.name);
 
     if (layout.type1 != PAYLOAD_NONE)
-        out.printf(" (");
-    dumpPayload(out, layout.type1, arg1_);
+        fprintf(fp, " (");
+    dumpPayload(fp, layout.type1, arg1_);
     if (layout.type2 != PAYLOAD_NONE)
-        out.printf(", ");
-    dumpPayload(out, layout.type2, arg2_);
+        fprintf(fp, ", ");
+    dumpPayload(fp, layout.type2, arg2_);
     if (layout.type1 != PAYLOAD_NONE)
-        out.printf(")");
+        fprintf(fp, ")");
 }
 
 bool
 RValueAllocation::equalPayloads(PayloadType type, Payload lhs, Payload rhs)
 {
     switch (type) {
       case PAYLOAD_NONE:
         return true;
@@ -545,23 +543,22 @@ SnapshotReader::readTrackSnapshot()
     lirId_     = reader_.readUnsigned();
 }
 
 void
 SnapshotReader::spewBailingFrom() const
 {
     if (JitSpewEnabled(JitSpew_IonBailouts)) {
         JitSpewHeader(JitSpew_IonBailouts);
-        Fprinter& out = JitSpewPrinter();
-        out.printf(" bailing from bytecode: %s, MIR: ", js_CodeName[pcOpcode_]);
-        MDefinition::PrintOpcodeName(out, MDefinition::Opcode(mirOpcode_));
-        out.printf(" [%u], LIR: ", mirId_);
-        LInstruction::printName(out, LInstruction::Opcode(lirOpcode_));
-        out.printf(" [%u]", lirId_);
-        out.printf("\n");
+        fprintf(JitSpewFile, " bailing from bytecode: %s, MIR: ", js_CodeName[pcOpcode_]);
+        MDefinition::PrintOpcodeName(JitSpewFile, MDefinition::Opcode(mirOpcode_));
+        fprintf(JitSpewFile, " [%u], LIR: ", mirId_);
+        LInstruction::printName(JitSpewFile, LInstruction::Opcode(lirOpcode_));
+        fprintf(JitSpewFile, " [%u]", lirId_);
+        fprintf(JitSpewFile, "\n");
     }
 }
 #endif
 
 uint32_t
 SnapshotReader::readAllocationIndex()
 {
     allocRead_++;
@@ -664,20 +661,19 @@ SnapshotWriter::add(const RValueAllocati
         if (!allocMap_.add(p, alloc, offset))
             return false;
     } else {
         offset = p->value();
     }
 
     if (JitSpewEnabled(JitSpew_IonSnapshots)) {
         JitSpewHeader(JitSpew_IonSnapshots);
-        Fprinter& out = JitSpewPrinter();
-        out.printf("    slot %u (%d): ", allocWritten_, offset);
-        alloc.dump(out);
-        out.printf("\n");
+        fprintf(JitSpewFile, "    slot %u (%d): ", allocWritten_, offset);
+        alloc.dump(JitSpewFile);
+        fprintf(JitSpewFile, "\n");
     }
 
     allocWritten_++;
     writer_.writeUnsigned(offset / ALLOCATION_TABLE_ALIGNMENT);
     return true;
 }
 
 void
--- a/js/src/jit/Snapshots.h
+++ b/js/src/jit/Snapshots.h
@@ -14,18 +14,16 @@
 
 #include "jit/CompactBuffer.h"
 #include "jit/IonTypes.h"
 #include "jit/Registers.h"
 
 #include "js/HashTable.h"
 
 namespace js {
-class GenericPrinter;
-
 namespace jit {
 
 class RValueAllocation;
 
 // A Recover Value Allocation mirror what is known at compiled time as being the
 // MIRType and the LAllocation.  This is read out of the snapshot to recover the
 // value which would be there if this frame was an interpreter frame instead of
 // an Ion frame.
@@ -162,17 +160,17 @@ class RValueAllocation
 
     static const Layout& layoutFromMode(Mode mode);
 
     static void readPayload(CompactBufferReader& reader, PayloadType t,
                             uint8_t* mode, Payload* p);
     static void writePayload(CompactBufferWriter& writer, PayloadType t,
                              Payload p);
     static void writePadding(CompactBufferWriter& writer);
-    static void dumpPayload(GenericPrinter& out, PayloadType t, Payload p);
+    static void dumpPayload(FILE* fp, PayloadType t, Payload p);
     static bool equalPayloads(PayloadType t, Payload lhs, Payload rhs);
 
     RValueAllocation(Mode mode, Payload a1, Payload a2)
       : mode_(mode),
         arg1_(a1),
         arg2_(a2)
     {
     }
@@ -331,17 +329,17 @@ class RValueAllocation
         return arg2_.stackOffset;
     }
     Register reg2() const {
         MOZ_ASSERT(layoutFromMode(mode()).type2 == PAYLOAD_GPR);
         return arg2_.gpr;
     }
 
   public:
-    void dump(GenericPrinter& out) const;
+    void dump(FILE* fp) const;
 
   public:
     bool operator==(const RValueAllocation& rhs) const {
         if (mode_ != rhs.mode_)
             return false;
 
         const Layout& layout = layoutFromMode(mode());
         return equalPayloads(layout.type1, arg1_, rhs.arg1_) &&
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -8,16 +8,17 @@
  * JS bytecode descriptors, disassemblers, and (expression) decompilers.
  */
 
 #include "jsopcodeinlines.h"
 
 #include "mozilla/SizePrintfMacros.h"
 
 #include <ctype.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsfun.h"
@@ -819,16 +820,19 @@ js::DumpScript(JSContext* cx, JSScript* 
     if (!sprinter.init())
         return false;
     RootedScript script(cx, scriptArg);
     bool ok = Disassemble(cx, script, true, &sprinter);
     fprintf(stdout, "%s", sprinter.string());
     return ok;
 }
 
+static char*
+QuoteString(Sprinter* sp, JSString* str, char16_t quote);
+
 static bool
 ToDisassemblySource(JSContext* cx, HandleValue v, JSAutoByteString* bytes)
 {
     if (v.isString()) {
         Sprinter sprinter(cx);
         if (!sprinter.init())
             return false;
         char* nbytes = QuoteString(&sprinter, v.toString(), '"');
@@ -1078,16 +1082,346 @@ js::Disassemble1(JSContext* cx, HandleSc
       }
     }
     sp->put("\n");
     return len;
 }
 
 #endif /* DEBUG */
 
+/************************************************************************/
+
+const size_t Sprinter::DefaultSize = 64;
+
+bool
+Sprinter::realloc_(size_t newSize)
+{
+    MOZ_ASSERT(newSize > (size_t) offset);
+    char* newBuf = (char*) js_realloc(base, newSize);
+    if (!newBuf) {
+        reportOutOfMemory();
+        return false;
+    }
+    base = newBuf;
+    size = newSize;
+    base[size - 1] = 0;
+    return true;
+}
+
+Sprinter::Sprinter(ExclusiveContext* cx)
+  : context(cx),
+#ifdef DEBUG
+    initialized(false),
+#endif
+    base(nullptr), size(0), offset(0), reportedOOM(false)
+{ }
+
+Sprinter::~Sprinter()
+{
+#ifdef DEBUG
+    if (initialized)
+        checkInvariants();
+#endif
+    js_free(base);
+}
+
+bool
+Sprinter::init()
+{
+    MOZ_ASSERT(!initialized);
+    base = (char*) js_malloc(DefaultSize);
+    if (!base) {
+        reportOutOfMemory();
+        return false;
+    }
+#ifdef DEBUG
+    initialized = true;
+#endif
+    *base = 0;
+    size = DefaultSize;
+    base[size - 1] = 0;
+    return true;
+}
+
+void
+Sprinter::checkInvariants() const
+{
+    MOZ_ASSERT(initialized);
+    MOZ_ASSERT((size_t) offset < size);
+    MOZ_ASSERT(base[size - 1] == 0);
+}
+
+const char*
+Sprinter::string() const
+{
+    return base;
+}
+
+const char*
+Sprinter::stringEnd() const
+{
+    return base + offset;
+}
+
+char*
+Sprinter::stringAt(ptrdiff_t off) const
+{
+    MOZ_ASSERT(off >= 0 && (size_t) off < size);
+    return base + off;
+}
+
+char&
+Sprinter::operator[](size_t off)
+{
+    MOZ_ASSERT(off < size);
+    return *(base + off);
+}
+
+char*
+Sprinter::reserve(size_t len)
+{
+    InvariantChecker ic(this);
+
+    while (len + 1 > size - offset) { /* Include trailing \0 */
+        if (!realloc_(size * 2))
+            return nullptr;
+    }
+
+    char* sb = base + offset;
+    offset += len;
+    return sb;
+}
+
+ptrdiff_t
+Sprinter::put(const char* s, size_t len)
+{
+    InvariantChecker ic(this);
+
+    const char* oldBase = base;
+    const char* oldEnd = base + size;
+
+    ptrdiff_t oldOffset = offset;
+    char* bp = reserve(len);
+    if (!bp)
+        return -1;
+
+    /* s is within the buffer already */
+    if (s >= oldBase && s < oldEnd) {
+        /* buffer was realloc'ed */
+        if (base != oldBase)
+            s = stringAt(s - oldBase);  /* this is where it lives now */
+        memmove(bp, s, len);
+    } else {
+        js_memcpy(bp, s, len);
+    }
+
+    bp[len] = 0;
+    return oldOffset;
+}
+
+ptrdiff_t
+Sprinter::put(const char* s)
+{
+    return put(s, strlen(s));
+}
+
+ptrdiff_t
+Sprinter::putString(JSString* s)
+{
+    InvariantChecker ic(this);
+
+    size_t length = s->length();
+    size_t size = length;
+
+    ptrdiff_t oldOffset = offset;
+    char* buffer = reserve(size);
+    if (!buffer)
+        return -1;
+
+    JSLinearString* linear = s->ensureLinear(context);
+    if (!linear)
+        return -1;
+
+    AutoCheckCannotGC nogc;
+    if (linear->hasLatin1Chars())
+        mozilla::PodCopy(reinterpret_cast<Latin1Char*>(buffer), linear->latin1Chars(nogc), length);
+    else
+        DeflateStringToBuffer(nullptr, linear->twoByteChars(nogc), length, buffer, &size);
+
+    buffer[size] = 0;
+    return oldOffset;
+}
+
+int
+Sprinter::printf(const char* fmt, ...)
+{
+    InvariantChecker ic(this);
+
+    do {
+        va_list va;
+        va_start(va, fmt);
+        int i = vsnprintf(base + offset, size - offset, fmt, va);
+        va_end(va);
+
+        if (i > -1 && (size_t) i < size - offset) {
+            offset += i;
+            return i;
+        }
+    } while (realloc_(size * 2));
+
+    return -1;
+}
+
+ptrdiff_t
+Sprinter::getOffset() const
+{
+    return offset;
+}
+
+void
+Sprinter::reportOutOfMemory()
+{
+    if (reportedOOM)
+        return;
+    if (context)
+        ReportOutOfMemory(context);
+    reportedOOM = true;
+}
+
+bool
+Sprinter::hadOutOfMemory() const
+{
+    return reportedOOM;
+}
+
+ptrdiff_t
+js::Sprint(Sprinter* sp, const char* format, ...)
+{
+    va_list ap;
+    char* bp;
+    ptrdiff_t offset;
+
+    va_start(ap, format);
+    bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
+    va_end(ap);
+    if (!bp) {
+        sp->reportOutOfMemory();
+        return -1;
+    }
+    offset = sp->put(bp);
+    js_free(bp);
+    return offset;
+}
+
+const char js_EscapeMap[] = {
+    '\b', 'b',
+    '\f', 'f',
+    '\n', 'n',
+    '\r', 'r',
+    '\t', 't',
+    '\v', 'v',
+    '"',  '"',
+    '\'', '\'',
+    '\\', '\\',
+    '\0'
+};
+
+template <typename CharT>
+static char*
+QuoteString(Sprinter* sp, const CharT* s, size_t length, char16_t quote)
+{
+    /* Sample off first for later return value pointer computation. */
+    ptrdiff_t offset = sp->getOffset();
+
+    if (quote && Sprint(sp, "%c", char(quote)) < 0)
+        return nullptr;
+
+    const CharT* end = s + length;
+
+    /* Loop control variables: end points at end of string sentinel. */
+    for (const CharT* t = s; t < end; s = ++t) {
+        /* Move t forward from s past un-quote-worthy characters. */
+        char16_t c = *t;
+        while (c < 127 && isprint(c) && c != quote && c != '\\' && c != '\t') {
+            c = *++t;
+            if (t == end)
+                break;
+        }
+
+        {
+            ptrdiff_t len = t - s;
+            ptrdiff_t base = sp->getOffset();
+            if (!sp->reserve(len))
+                return nullptr;
+
+            for (ptrdiff_t i = 0; i < len; ++i)
+                (*sp)[base + i] = char(*s++);
+            (*sp)[base + len] = 0;
+        }
+
+        if (t == end)
+            break;
+
+        /* Use js_EscapeMap, \u, or \x only if necessary. */
+        const char* escape;
+        if (!(c >> 8) && c != 0 && (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
+            if (Sprint(sp, "\\%c", escape[1]) < 0)
+                return nullptr;
+        } else {
+            /*
+             * Use \x only if the high byte is 0 and we're in a quoted string,
+             * because ECMA-262 allows only \u, not \x, in Unicode identifiers
+             * (see bug 621814).
+             */
+            if (Sprint(sp, (quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) < 0)
+                return nullptr;
+        }
+    }
+
+    /* Sprint the closing quote and return the quoted string. */
+    if (quote && Sprint(sp, "%c", char(quote)) < 0)
+        return nullptr;
+
+    /*
+     * If we haven't Sprint'd anything yet, Sprint an empty string so that
+     * the return below gives a valid result.
+     */
+    if (offset == sp->getOffset() && Sprint(sp, "") < 0)
+        return nullptr;
+
+    return sp->stringAt(offset);
+}
+
+static char*
+QuoteString(Sprinter* sp, JSString* str, char16_t quote)
+{
+    JSLinearString* linear = str->ensureLinear(sp->context);
+    if (!linear)
+        return nullptr;
+
+    AutoCheckCannotGC nogc;
+    return linear->hasLatin1Chars()
+           ? QuoteString(sp, linear->latin1Chars(nogc), linear->length(), quote)
+           : QuoteString(sp, linear->twoByteChars(nogc), linear->length(), quote);
+}
+
+JSString*
+js::QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote)
+{
+    Sprinter sprinter(cx);
+    if (!sprinter.init())
+        return nullptr;
+    char* bytes = QuoteString(&sprinter, str, quote);
+    if (!bytes)
+        return nullptr;
+    return NewStringCopyZ<CanGC>(cx, bytes);
+}
+
+/************************************************************************/
+
 namespace {
 /*
  * The expression decompiler is invoked by error handling code to produce a
  * string representation of the erroring expression. As it's only a debugging
  * tool, it only supports basic expressions. For anything complicated, it simply
  * puts "(intermediate value)" into the error result.
  *
  * Here's the basic algorithm:
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -14,17 +14,16 @@
 #include "mozilla/UniquePtr.h"
 
 #include "jsbytecode.h"
 #include "jstypes.h"
 #include "NamespaceImports.h"
 
 #include "frontend/SourceNotes.h"
 #include "vm/Opcodes.h"
-#include "vm/Printer.h"
 
 /*
  * JS operation bytecodes.
  */
 typedef enum JSOp {
 #define ENUMERATE_OPCODE(op, val, ...) op = val,
 FOR_EACH_OPCODE(ENUMERATE_OPCODE)
 #undef ENUMERATE_OPCODE
@@ -378,16 +377,17 @@ struct JSCodeSpec {
     uint32_t            format;         /* immediate operand format */
 
     uint32_t type() const { return JOF_TYPE(format); }
 };
 
 extern const JSCodeSpec js_CodeSpec[];
 extern const unsigned   js_NumCodeSpecs;
 extern const char       * const js_CodeName[];
+extern const char       js_EscapeMap[];
 
 /* Shorthand for type from opcode. */
 
 static inline uint32_t
 JOF_OPTYPE(JSOp op)
 {
     return JOF_TYPE(js_CodeSpec[op].format);
 }
@@ -395,16 +395,24 @@ JOF_OPTYPE(JSOp op)
 /* Silence unreferenced formal parameter warnings */
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100)
 #endif
 
 namespace js {
 
+/*
+ * Return a GC'ed string containing the chars in str, with any non-printing
+ * chars or quotes (' or " as specified by the quote argument) escaped, and
+ * with the quote character at the beginning and end of the result string.
+ */
+extern JSString*
+QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote);
+
 static inline bool
 IsJumpOpcode(JSOp op)
 {
     uint32_t type = JOF_TYPE(js_CodeSpec[op].format);
 
     /*
      * LABEL opcodes have type JOF_JUMP but are no-ops, don't treat them as
      * jumps to avoid degrading precision.
@@ -564,16 +572,99 @@ DecompileValueGenerator(JSContext* cx, i
 
 /*
  * Decompile the formal argument at formalIndex in the nearest non-builtin
  * stack frame, falling back with converting v to source.
  */
 char*
 DecompileArgument(JSContext* cx, int formalIndex, HandleValue v);
 
+/*
+ * Sprintf, but with unlimited and automatically allocated buffering.
+ */
+class Sprinter
+{
+  public:
+    struct InvariantChecker
+    {
+        const Sprinter* parent;
+
+        explicit InvariantChecker(const Sprinter* p) : parent(p) {
+            parent->checkInvariants();
+        }
+
+        ~InvariantChecker() {
+            parent->checkInvariants();
+        }
+    };
+
+    ExclusiveContext*       context;       /* context executing the decompiler */
+
+  private:
+    static const size_t     DefaultSize;
+#ifdef DEBUG
+    bool                    initialized;    /* true if this is initialized, use for debug builds */
+#endif
+    char*                   base;          /* malloc'd buffer address */
+    size_t                  size;           /* size of buffer allocated at base */
+    ptrdiff_t               offset;         /* offset of next free char in buffer */
+    bool                    reportedOOM;    /* this sprinter has reported OOM in string ops */
+
+    bool realloc_(size_t newSize);
+
+  public:
+    explicit Sprinter(ExclusiveContext* cx);
+    ~Sprinter();
+
+    /* Initialize this sprinter, returns false on error */
+    bool init();
+
+    void checkInvariants() const;
+
+    const char* string() const;
+    const char* stringEnd() const;
+    /* Returns the string at offset |off| */
+    char* stringAt(ptrdiff_t off) const;
+    /* Returns the char at offset |off| */
+    char& operator[](size_t off);
+
+    /*
+     * Attempt to reserve len + 1 space (for a trailing nullptr byte). If the
+     * attempt succeeds, return a pointer to the start of that space and adjust the
+     * internal content. The caller *must* completely fill this space on success.
+     */
+    char* reserve(size_t len);
+
+    /*
+     * Puts |len| characters from |s| at the current position and return an offset to
+     * the beginning of this new data
+     */
+    ptrdiff_t put(const char* s, size_t len);
+    ptrdiff_t put(const char* s);
+    ptrdiff_t putString(JSString* str);
+
+    /* Prints a formatted string into the buffer */
+    int printf(const char* fmt, ...);
+
+    ptrdiff_t getOffset() const;
+
+    /*
+     * Report that a string operation failed to get the memory it requested. The
+     * first call to this function calls JS_ReportOutOfMemory, and sets this
+     * Sprinter's outOfMemory flag; subsequent calls do nothing.
+     */
+    void reportOutOfMemory();
+
+    /* Return true if this Sprinter ran out of memory. */
+    bool hadOutOfMemory() const;
+};
+
+extern ptrdiff_t
+Sprint(Sprinter* sp, const char* format, ...);
+
 extern bool
 CallResultEscapes(jsbytecode* pc);
 
 static inline unsigned
 GetDecomposeLength(jsbytecode* pc, size_t len)
 {
     /*
      * The last byte of a DECOMPOSE op stores the decomposed length.  This is a
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1368,16 +1368,17 @@ class JSScript : public js::gc::TenuredC
     }
     js::jit::IonScript* const* addressOfIonScript() const {
         return &ion;
     }
     void setIonScript(JSContext* maybecx, js::jit::IonScript* ionScript) {
         if (hasIonScript())
             js::jit::IonScript::writeBarrierPre(zone(), ion);
         ion = ionScript;
+        resetWarmUpResetCounter();
         MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
         updateBaselineOrIonRaw(maybecx);
     }
 
     bool hasBaselineScript() const {
         bool res = baseline && baseline != BASELINE_DISABLED_SCRIPT;
         MOZ_ASSERT_IF(!res, !ion || ion == ION_DISABLED_SCRIPT);
         return res;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -34,17 +34,16 @@
 #include "builtin/RegExp.h"
 #include "js/Conversions.h"
 #if ENABLE_INTL_API
 #include "unicode/unorm.h"
 #endif
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Opcodes.h"
-#include "vm/Printer.h"
 #include "vm/RegExpObject.h"
 #include "vm/RegExpStatics.h"
 #include "vm/ScopeObject.h"
 #include "vm/StringBuffer.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/String-inl.h"
 #include "vm/StringObject-inl.h"
@@ -5035,38 +5034,38 @@ js::OneUcs4ToUtf8Char(uint8_t* utf8Buffe
             ucs4Char >>= 6;
         }
         *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
     }
     return utf8Length;
 }
 
 size_t
-js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, JSLinearString* str,
+js::PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, JSLinearString* str,
                          uint32_t quote)
 {
     size_t len = str->length();
     AutoCheckCannotGC nogc;
     return str->hasLatin1Chars()
-           ? PutEscapedStringImpl(buffer, bufferSize, out, str->latin1Chars(nogc), len, quote)
-           : PutEscapedStringImpl(buffer, bufferSize, out, str->twoByteChars(nogc), len, quote);
+           ? PutEscapedStringImpl(buffer, bufferSize, fp, str->latin1Chars(nogc), len, quote)
+           : PutEscapedStringImpl(buffer, bufferSize, fp, str->twoByteChars(nogc), len, quote);
 }
 
 template <typename CharT>
 size_t
-js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const CharT* chars,
+js::PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, const CharT* chars,
                          size_t length, uint32_t quote)
 {
     enum {
         STOP, FIRST_QUOTE, LAST_QUOTE, CHARS, ESCAPE_START, ESCAPE_MORE
     } state;
 
     MOZ_ASSERT(quote == 0 || quote == '\'' || quote == '"');
     MOZ_ASSERT_IF(!buffer, bufferSize == 0);
-    MOZ_ASSERT_IF(out, !buffer);
+    MOZ_ASSERT_IF(fp, !buffer);
 
     if (bufferSize == 0)
         buffer = nullptr;
     else
         bufferSize--;
 
     const CharT* charsEnd = chars + length;
     size_t n = 0;
@@ -5145,38 +5144,38 @@ js::PutEscapedStringImpl(char* buffer, s
         if (buffer) {
             MOZ_ASSERT(n <= bufferSize);
             if (n != bufferSize) {
                 buffer[n] = c;
             } else {
                 buffer[n] = '\0';
                 buffer = nullptr;
             }
-        } else if (out) {
-            if (out->put(&c, 1) < 0)
+        } else if (fp) {
+            if (fputc(c, fp) < 0)
                 return size_t(-1);
         }
         n++;
     }
   stop:
     if (buffer)
         buffer[n] = '\0';
     return n;
 }
 
 template size_t
-js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const Latin1Char* chars,
+js::PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, const Latin1Char* chars,
                          size_t length, uint32_t quote);
 
 template size_t
-js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char* chars,
+js::PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, const char* chars,
                          size_t length, uint32_t quote);
 
 template size_t
-js::PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const char16_t* chars,
+js::PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, const char16_t* chars,
                          size_t length, uint32_t quote);
 
 template size_t
 js::PutEscapedString(char* buffer, size_t bufferSize, const Latin1Char* chars, size_t length,
                      uint32_t quote);
 
 template size_t
 js::PutEscapedString(char* buffer, size_t bufferSize, const char16_t* chars, size_t length,
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -11,17 +11,16 @@
 #include "mozilla/PodOperations.h"
 #include "mozilla/UniquePtr.h"
 
 #include "jsutil.h"
 #include "NamespaceImports.h"
 
 #include "gc/Rooting.h"
 #include "js/RootingAPI.h"
-#include "vm/Printer.h"
 #include "vm/Unicode.h"
 
 class JSAutoByteString;
 class JSLinearString;
 
 namespace js {
 
 class StringBuffer;
@@ -338,22 +337,21 @@ str_charCodeAt(JSContext* cx, unsigned a
 /*
  * Convert one UCS-4 char and write it into a UTF-8 buffer, which must be at
  * least 6 bytes long.  Return the number of UTF-8 bytes of data written.
  */
 extern int
 OneUcs4ToUtf8Char(uint8_t* utf8Buffer, uint32_t ucs4Char);
 
 extern size_t
-PutEscapedStringImpl(char* buffer, size_t size, GenericPrinter* out, JSLinearString* str,
-                     uint32_t quote);
+PutEscapedStringImpl(char* buffer, size_t size, FILE* fp, JSLinearString* str, uint32_t quote);
 
 template <typename CharT>
 extern size_t
-PutEscapedStringImpl(char* buffer, size_t bufferSize, GenericPrinter* out, const CharT* chars,
+PutEscapedStringImpl(char* buffer, size_t bufferSize, FILE* fp, const CharT* chars,
                      size_t length, uint32_t quote);
 
 /*
  * Write str into buffer escaping any non-printable or non-ASCII character
  * using \escapes for JS string literals.
  * Guarantees that a NUL is at the end of the buffer unless size is 0. Returns
  * the length of the written output, NOT including the NUL. Thus, a return
  * value of size or more means that the output was truncated. If buffer
@@ -376,49 +374,31 @@ PutEscapedString(char* buffer, size_t bu
 {
     size_t n = PutEscapedStringImpl(buffer, bufferSize, nullptr, chars, length, quote);
 
     /* PutEscapedStringImpl can only fail with a file. */
     MOZ_ASSERT(n != size_t(-1));
     return n;
 }
 
-inline bool
-EscapedStringPrinter(GenericPrinter& out, JSLinearString* str, uint32_t quote)
-{
-    return PutEscapedStringImpl(nullptr, 0, &out, str, quote) != size_t(-1);
-}
-
-inline bool
-EscapedStringPrinter(GenericPrinter& out, const char* chars, size_t length, uint32_t quote)
-{
-    return PutEscapedStringImpl(nullptr, 0, &out, chars, length, quote) != size_t(-1);
-}
-
 /*
  * Write str into file escaping any non-printable or non-ASCII character.
  * If quote is not 0, it must be a single or double quote character that
  * will quote the output.
 */
 inline bool
 FileEscapedString(FILE* fp, JSLinearString* str, uint32_t quote)
 {
-    Fprinter out(fp);
-    bool res = EscapedStringPrinter(out, str, quote);
-    out.finish();
-    return res;
+    return PutEscapedStringImpl(nullptr, 0, fp, str, quote) != size_t(-1);
 }
 
 inline bool
 FileEscapedString(FILE* fp, const char* chars, size_t length, uint32_t quote)
 {
-    Fprinter out(fp);
-    bool res = EscapedStringPrinter(out, chars, length, quote);
-    out.finish();
-    return res;
+    return PutEscapedStringImpl(nullptr, 0, fp, chars, length, quote) != size_t(-1);
 }
 
 bool
 str_match(JSContext* cx, unsigned argc, Value* vp);
 
 bool
 str_search(JSContext* cx, unsigned argc, Value* vp);
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -290,17 +290,16 @@ UNIFIED_SOURCES += [
     'vm/Id.cpp',
     'vm/Interpreter.cpp',
     'vm/JSONParser.cpp',
     'vm/MemoryMetrics.cpp',
     'vm/Monitor.cpp',
     'vm/NativeObject.cpp',
     'vm/ObjectGroup.cpp',
     'vm/PIC.cpp',
-    'vm/Printer.cpp',
     'vm/Probes.cpp',
     'vm/ProxyObject.cpp',
     'vm/ReceiverGuard.cpp',
     'vm/RegExpObject.cpp',
     'vm/RegExpStatics.cpp',
     'vm/Runtime.cpp',
     'vm/SavedStacks.cpp',
     'vm/ScopeObject.cpp',
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -53,32 +53,9 @@ js::Debugger::onDebuggerStatement(JSCont
 /* static */ JSTrapStatus
 js::Debugger::onExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
 {
     if (!cx->compartment()->isDebuggee())
         return JSTRAP_CONTINUE;
     return slowPathOnExceptionUnwind(cx, frame);
 }
 
-/* static */ bool
-js::Debugger::observesIonCompilation(JSContext* cx)
-{
-    // If the current compartment is observed by any Debugger.
-    if (!cx->compartment()->isDebuggee())
-        return false;
-
-    // If any attached Debugger watch for Jit compilation results.
-    if (!Debugger::hasLiveHook(cx->global(), Debugger::OnIonCompilation))
-        return false;
-
-    return true;
-}
-
-/* static */ void
-js::Debugger::onIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
-{
-    if (!observesIonCompilation(cx))
-        return;
-
-    slowPathOnIonCompilation(cx, scripts, graph);
-}
-
 #endif /* vm_Debugger_inl_h */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -15,18 +15,16 @@
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jswrapper.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "gc/Marking.h"
 #include "jit/BaselineDebugModeOSR.h"
 #include "jit/BaselineJIT.h"
-#include "jit/JSONSpewer.h"
-#include "jit/MIRGraph.h"
 #include "js/GCAPI.h"
 #include "js/UbiNodeTraverse.h"
 #include "js/Vector.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/DebuggerMemory.h"
 #include "vm/SPSProfiler.h"
 #include "vm/TraceLogging.h"
 #include "vm/WrapperObject.h"
@@ -537,25 +535,43 @@ Debugger::hasAnyLiveHooks() const
     }
 
     return false;
 }
 
 /* static */ JSTrapStatus
 Debugger::slowPathOnEnterFrame(JSContext* cx, AbstractFramePtr frame)
 {
+    // Build the list of recipients.
+    AutoValueVector triggered(cx);
+    Handle<GlobalObject*> global = cx->global();
+
+    if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
+        for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
+            Debugger* dbg = *p;
+            if (dbg->observesFrame(frame) && dbg->observesEnterFrame() &&
+                !triggered.append(ObjectValue(*dbg->toJSObject())))
+            {
+                cx->clearPendingException();
+                return JSTRAP_ERROR;
+            }
+        }
+    }
+
+    JSTrapStatus status = JSTRAP_CONTINUE;
     RootedValue rval(cx);
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [frame](Debugger* dbg) -> bool {
-            return dbg->observesFrame(frame) && dbg->observesEnterFrame();
-        },
-        [&](Debugger* dbg) -> JSTrapStatus {
-            return dbg->fireEnterFrame(cx, frame, &rval);
-        });
+    // Deliver the event, checking again as in dispatchHook.
+    for (Value* p = triggered.begin(); p != triggered.end(); p++) {
+        Debugger* dbg = Debugger::fromJSObject(&p->toObject());
+        if (dbg->debuggees.has(global) && dbg->observesEnterFrame()) {
+            status = dbg->fireEnterFrame(cx, frame, &rval);
+            if (status != JSTRAP_CONTINUE)
+                break;
+        }
+    }
 
     switch (status) {
       case JSTRAP_CONTINUE:
         break;
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         break;
@@ -684,22 +700,17 @@ Debugger::slowPathOnLeaveFrame(JSContext
         MOZ_CRASH("bad final trap status");
     }
 }
 
 /* static */ JSTrapStatus
 Debugger::slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame)
 {
     RootedValue rval(cx);
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [](Debugger* dbg) -> bool { return dbg->getHook(OnDebuggerStatement); },
-        [&](Debugger* dbg) -> JSTrapStatus {
-            return dbg->fireDebuggerStatement(cx, &rval);
-        });
+    JSTrapStatus status = dispatchHook(cx, &rval, OnDebuggerStatement, NullPtr());
 
     switch (status) {
       case JSTRAP_CONTINUE:
       case JSTRAP_ERROR:
         break;
 
       case JSTRAP_RETURN:
         frame.setReturnValue(rval);
@@ -720,22 +731,17 @@ Debugger::slowPathOnDebuggerStatement(JS
 Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
 {
     // Invoking more JS on an over-recursed stack or after OOM is only going
     // to result in more of the same error.
     if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
         return JSTRAP_CONTINUE;
 
     RootedValue rval(cx);
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [](Debugger* dbg) -> bool { return dbg->getHook(OnExceptionUnwind); },
-        [&](Debugger* dbg) -> JSTrapStatus {
-            return dbg->fireExceptionUnwind(cx, &rval);
-        });
+    JSTrapStatus status = dispatchHook(cx, &rval, OnExceptionUnwind, NullPtr());
 
     switch (status) {
       case JSTRAP_CONTINUE:
         break;
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         break;
@@ -1281,144 +1287,110 @@ Debugger::fireOnGarbageCollectionHook(JS
     }
 
     RootedValue dataVal(cx, ObjectValue(*dataObj));
     RootedValue rv(cx);
     if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, dataVal.address(), &rv))
         handleUncaughtException(ac, true);
 }
 
-JSTrapStatus
-Debugger::fireOnIonCompilationHook(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
-{
-    RootedObject hook(cx, getHook(OnIonCompilation));
-    MOZ_ASSERT(hook);
-    MOZ_ASSERT(hook->isCallable());
-
-    Maybe<AutoCompartment> ac;
-    ac.emplace(cx, object);
-
-    // Copy the vector of scripts to a JS Array of Debugger.Script
-    RootedObject tmpObj(cx);
-    RootedValue tmpVal(cx);
-    AutoValueVector dbgScripts(cx);
-    for (size_t i = 0; i < scripts.length(); i++) {
-        tmpObj = wrapScript(cx, scripts[i]);
-        if (!tmpObj)
-            return handleUncaughtException(ac, false);
-
-        tmpVal.setObject(*tmpObj);
-        if (!dbgScripts.append(tmpVal))
-            return handleUncaughtException(ac, false);
-    }
-
-    RootedObject dbgScriptsArray(cx, JS_NewArrayObject(cx, dbgScripts));
-    if (!dbgScriptsArray)
-        return handleUncaughtException(ac, false);
-
-    // Copy the JSON compilation graph to a JS String which is allocated as part
-    // of the Debugger compartment.
-    Sprinter jsonPrinter(cx);
-    if (!jsonPrinter.init())
-        return handleUncaughtException(ac, false);
-
-    graph.exportInto(jsonPrinter);
-    if (jsonPrinter.hadOutOfMemory())
-        return handleUncaughtException(ac, false);
-
-    RootedString json(cx, JS_NewStringCopyZ(cx, jsonPrinter.string()));
-    if (!json)
-        return handleUncaughtException(ac, false);
-
-    // Create a JS Object which has the array of scripts, and the string of the
-    // JSON graph.
-    const char* names[] = { "scripts", "json" };
-    JS::AutoValueArray<2> values(cx);
-    values[0].setObject(*dbgScriptsArray);
-    values[1].setString(json);
-
-    RootedObject obj(cx, JS_NewObject(cx, nullptr));
-    if (!obj)
-        return handleUncaughtException(ac, false);
-
-    MOZ_ASSERT(mozilla::ArrayLength(names) == values.length());
-    for (size_t i = 0; i < mozilla::ArrayLength(names); i++) {
-        if (!JS_DefineProperty(cx, obj, names[i], values[i], JSPROP_ENUMERATE, nullptr, nullptr))
-            return handleUncaughtException(ac, false);
-    }
-
-    // Call Debugger.onIonCompilation hook.
-    JS::AutoValueArray<1> argv(cx);
-    argv[0].setObject(*obj);
-
-    RootedValue rv(cx);
-    if (!Invoke(cx, ObjectValue(*object), ObjectValue(*hook), 1, argv.begin(), &rv))
-        return handleUncaughtException(ac, true);
-    return JSTRAP_CONTINUE;
-}
-
-template <typename HookIsEnabledFun /* bool (Debugger*) */,
-          typename FireHookFun /* JSTrapStatus (Debugger*) */>
 /* static */ JSTrapStatus
-Debugger::dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled, FireHookFun fireHook)
-{
+Debugger::dispatchHook(JSContext* cx, MutableHandleValue vp, Hook which, HandleObject payload)
+{
+    MOZ_ASSERT(which == OnDebuggerStatement ||
+               which == OnExceptionUnwind ||
+               which == OnNewPromise ||
+               which == OnPromiseSettled);
+
     /*
      * Determine which debuggers will receive this event, and in what order.
      * Make a copy of the list, since the original is mutable and we will be
      * calling into arbitrary JS.
      *
      * Note: In the general case, 'triggered' contains references to objects in
      * different compartments--every compartment *except* this one.
      */
     AutoValueVector triggered(cx);
     Handle<GlobalObject*> global = cx->global();
     if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
         for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
             Debugger* dbg = *p;
-            if (dbg->enabled && hookIsEnabled(dbg)) {
+            if (dbg->enabled && dbg->getHook(which)) {
                 if (!triggered.append(ObjectValue(*dbg->toJSObject())))
                     return JSTRAP_ERROR;
             }
         }
     }
 
     /*
      * Deliver the event to each debugger, checking again to make sure it
      * should still be delivered.
      */
     for (Value* p = triggered.begin(); p != triggered.end(); p++) {
         Debugger* dbg = Debugger::fromJSObject(&p->toObject());
-        if (dbg->debuggees.has(global) && dbg->enabled && hookIsEnabled(dbg)) {
-            JSTrapStatus st = fireHook(dbg);
+        if (dbg->debuggees.has(global) && dbg->enabled && dbg->getHook(which)) {
+            JSTrapStatus st;
+            switch (which) {
+              case OnDebuggerStatement:
+                st = dbg->fireDebuggerStatement(cx, vp);
+                break;
+              case OnExceptionUnwind:
+                st = dbg->fireExceptionUnwind(cx, vp);
+                break;
+              case OnNewPromise:
+              case OnPromiseSettled:
+                st = dbg->firePromiseHook(cx, which, payload, vp);
+                break;
+              default:
+                MOZ_ASSERT_UNREACHABLE("Unexpected debugger hook");
+                st = JSTRAP_CONTINUE;
+            }
             if (st != JSTRAP_CONTINUE)
                 return st;
         }
     }
     return JSTRAP_CONTINUE;
 }
 
 void
 Debugger::slowPathOnNewScript(JSContext* cx, HandleScript script)
 {
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [script](Debugger* dbg) -> bool {
-            return dbg->observesNewScript() && dbg->observesScript(script);
-        },
-        [&](Debugger* dbg) -> JSTrapStatus {
+    Rooted<GlobalObject*> global(cx, &script->global());
+
+    /*
+     * Build the list of recipients based on the debuggers observing the
+     * script's compartment.
+     */
+    AutoValueVector triggered(cx);
+    GlobalObject::DebuggerVector* debuggers = global->getDebuggers();
+    if (debuggers) {
+        for (Debugger** p = debuggers->begin(); p != debuggers->end(); p++) {
+            Debugger* dbg = *p;
+            if (dbg->observesNewScript() && dbg->observesScript(script)) {
+                if (!triggered.append(ObjectValue(*dbg->toJSObject()))) {
+                    ReportOutOfMemory(cx);
+                    return;
+                }
+            }
+        }
+    }
+
+    /*
+     * Deliver the event to each debugger, checking again as in
+     * Debugger::dispatchHook.
+     */
+    for (Value* p = triggered.begin(); p != triggered.end(); p++) {
+        Debugger* dbg = Debugger::fromJSObject(&p->toObject());
+        if (dbg->debuggees.has(global) &&
+            dbg->enabled &&
+            dbg->getHook(OnNewScript))
+        {
             dbg->fireNewScript(cx, script);
-            return JSTRAP_CONTINUE;
-        });
-
-    if (status == JSTRAP_ERROR) {
-        ReportOutOfMemory(cx);
-        return;
-    }
-
-    MOZ_ASSERT(status == JSTRAP_CONTINUE);
+        }
+    }
 }
 
 /* static */ JSTrapStatus
 Debugger::onTrap(JSContext* cx, MutableHandleValue vp)
 {
     ScriptFrameIter iter(cx);
     RootedScript script(cx, iter.script());
     MOZ_ASSERT(script->isDebuggee());
@@ -1668,35 +1640,16 @@ Debugger::slowPathOnLogAllocationSite(JS
         {
             return false;
         }
     }
 
     return true;
 }
 
-/* static */ void
-Debugger::slowPathOnIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph)
-{
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [](Debugger* dbg) -> bool { return dbg->getHook(OnIonCompilation); },
-        [&](Debugger* dbg) -> JSTrapStatus {
-            (void) dbg->fireOnIonCompilationHook(cx, scripts, graph);
-            return JSTRAP_CONTINUE;
-        });
-
-    if (status == JSTRAP_ERROR) {
-        cx->clearPendingException();
-        return;
-    }
-
-    MOZ_ASSERT(status == JSTRAP_CONTINUE);
-}
-
 bool
 Debugger::isDebuggee(const JSCompartment* compartment) const
 {
     MOZ_ASSERT(compartment);
     return compartment->isDebuggee() && debuggees.has(compartment->maybeGlobal());
 }
 
 /* static */ Debugger::AllocationSite*
@@ -1790,34 +1743,19 @@ Debugger::firePromiseHook(JSContext* cx,
 }
 
 /* static */ void
 Debugger::slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise)
 {
     MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
     RootedValue rval(cx);
 
-    JSTrapStatus status = dispatchHook(
-        cx,
-        [hook](Debugger* dbg) -> bool { return dbg->getHook(hook); },
-        [&](Debugger* dbg) -> JSTrapStatus {
-            (void) dbg->firePromiseHook(cx, hook, promise, &rval);
-            return JSTRAP_CONTINUE;
-        });
-
-    if (status == JSTRAP_ERROR) {
-        // The dispatch hook function might fail to append into the list of
-        // Debuggers which are watching for the hook.
-        cx->clearPendingException();
-        return;
-    }
-
     // Promise hooks are infallible and we ignore errors from uncaught
     // exceptions by design.
-    MOZ_ASSERT(status == JSTRAP_CONTINUE);
+    (void) dispatchHook(cx, &rval, hook, promise);
 }
 
 
 /*** Debugger code invalidation for observing execution ******************************************/
 
 class MOZ_STACK_CLASS ExecutionObservableCompartments : public Debugger::ExecutionObservableSet
 {
     HashSet<JSCompartment*> compartments_;
@@ -2900,30 +2838,16 @@ Debugger::getMemory(JSContext* cx, unsig
             return false;
         memoryValue = ObjectValue(*memory);
     }
 
     args.rval().set(memoryValue);
     return true;
 }
 
-/* static */ bool
-Debugger::getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "(get onIonCompilation)", args, dbg);
-    return getHookImpl(cx, args, *dbg, OnIonCompilation);
-}
-
-/* static */ bool
-Debugger::setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp)
-{
-    THIS_DEBUGGER(cx, argc, vp, "(set onIonCompilation)", args, dbg);
-    return setHookImpl(cx, args, *dbg, OnIonCompilation);
-}
-
 /*
  * Given a value used to designate a global (there's quite a variety; see the
  * docs), return the actual designee.
  *
  * Note that this does not check whether the designee is marked "invisible to
  * Debugger" or not; different callers need to handle invisible-to-Debugger
  * globals in different ways.
  */
@@ -4329,17 +4253,16 @@ const JSPropertySpec Debugger::propertie
     JS_PSGS("onPromiseSettled", Debugger::getOnPromiseSettled, Debugger::setOnPromiseSettled, 0),
     JS_PSGS("onEnterFrame", Debugger::getOnEnterFrame, Debugger::setOnEnterFrame, 0),
     JS_PSGS("onNewGlobalObject", Debugger::getOnNewGlobalObject, Debugger::setOnNewGlobalObject, 0),
     JS_PSGS("uncaughtExceptionHook", Debugger::getUncaughtExceptionHook,
             Debugger::setUncaughtExceptionHook, 0),
     JS_PSGS("allowUnobservedAsmJS", Debugger::getAllowUnobservedAsmJS,
             Debugger::setAllowUnobservedAsmJS, 0),
     JS_PSG("memory", Debugger::getMemory, 0),
-    JS_PSGS("onIonCompilation", Debugger::getOnIonCompilation, Debugger::setOnIonCompilation, 0),
     JS_PS_END
 };
 const JSFunctionSpec Debugger::methods[] = {
     JS_FN("addDebuggee", Debugger::addDebuggee, 1, 0),
     JS_FN("addAllGlobalsAsDebuggees", Debugger::addAllGlobalsAsDebuggees, 0, 0),
     JS_FN("removeDebuggee", Debugger::removeDebuggee, 1, 0),
     JS_FN("removeAllDebuggees", Debugger::removeAllDebuggees, 0, 0),
     JS_FN("hasDebuggee", Debugger::hasDebuggee, 1, 0),
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -30,18 +30,16 @@ enum JSTrapStatus {
     JSTRAP_CONTINUE,
     JSTRAP_RETURN,
     JSTRAP_THROW,
     JSTRAP_LIMIT
 };
 
 namespace js {
 
-class LSprinter;
-
 class Breakpoint;
 class DebuggerMemory;
 
 typedef HashSet<ReadBarrieredGlobalObject,
                 DefaultHasher<ReadBarrieredGlobalObject>,
                 SystemAllocPolicy> WeakGlobalObjectSet;
 
 /*
@@ -201,17 +199,16 @@ class Debugger : private mozilla::Linked
         OnDebuggerStatement,
         OnExceptionUnwind,
         OnNewScript,
         OnEnterFrame,
         OnNewGlobalObject,
         OnNewPromise,
         OnPromiseSettled,
         OnGarbageCollection,
-        OnIonCompilation,
         HookCount
     };
     enum {
         JSSLOT_DEBUG_PROTO_START,
         JSSLOT_DEBUG_FRAME_PROTO = JSSLOT_DEBUG_PROTO_START,
         JSSLOT_DEBUG_ENV_PROTO,
         JSSLOT_DEBUG_OBJECT_PROTO,
         JSSLOT_DEBUG_SCRIPT_PROTO,
@@ -477,18 +474,16 @@ class Debugger : private mozilla::Linked
     static bool setOnNewPromise(JSContext* cx, unsigned argc, Value* vp);
     static bool getOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
     static bool setOnPromiseSettled(JSContext* cx, unsigned argc, Value* vp);
     static bool getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
     static bool setUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp);
     static bool getAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
     static bool setAllowUnobservedAsmJS(JSContext* cx, unsigned argc, Value* vp);
     static bool getMemory(JSContext* cx, unsigned argc, Value* vp);
-    static bool getOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
-    static bool setOnIonCompilation(JSContext* cx, unsigned argc, Value* vp);
     static bool addDebuggee(JSContext* cx, unsigned argc, Value* vp);
     static bool addAllGlobalsAsDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool removeDebuggee(JSContext* cx, unsigned argc, Value* vp);
     static bool removeAllDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool hasDebuggee(JSContext* cx, unsigned argc, Value* vp);
     static bool getDebuggees(JSContext* cx, unsigned argc, Value* vp);
     static bool getNewestFrame(JSContext* cx, unsigned argc, Value* vp);
     static bool clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp);
@@ -546,22 +541,18 @@ class Debugger : private mozilla::Linked
     static bool slowPathOnLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
     static JSTrapStatus slowPathOnDebuggerStatement(JSContext* cx, AbstractFramePtr frame);
     static JSTrapStatus slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame);
     static void slowPathOnNewScript(JSContext* cx, HandleScript script);
     static void slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static bool slowPathOnLogAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                                             int64_t when, GlobalObject::DebuggerVector& dbgs);
     static void slowPathPromiseHook(JSContext* cx, Hook hook, HandleObject promise);
-    static void slowPathOnIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
-
-    template <typename HookIsEnabledFun /* bool (Debugger*) */,
-              typename FireHookFun /* JSTrapStatus (Debugger*) */>
-    static JSTrapStatus dispatchHook(JSContext* cx, HookIsEnabledFun hookIsEnabled,
-                                     FireHookFun fireHook);
+    static JSTrapStatus dispatchHook(JSContext* cx, MutableHandleValue vp, Hook which,
+                                     HandleObject payload);
 
     JSTrapStatus fireDebuggerStatement(JSContext* cx, MutableHandleValue vp);
     JSTrapStatus fireExceptionUnwind(JSContext* cx, MutableHandleValue vp);
     JSTrapStatus fireEnterFrame(JSContext* cx, AbstractFramePtr frame, MutableHandleValue vp);
     JSTrapStatus fireNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global, MutableHandleValue vp);
     JSTrapStatus firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp);
 
     /*
@@ -585,23 +576,16 @@ class Debugger : private mozilla::Linked
     /*
      * Receive a "garbage collection" event from the engine. A GC cycle with the
      * given data was recently completed.
      */
     void fireOnGarbageCollectionHook(JSContext* cx,
                                      const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
 
     /*
-     * Receive a "Ion compilation" event from the engine. An Ion compilation with
-     * the given summary just got linked.
-     */
-    JSTrapStatus fireOnIonCompilationHook(JSContext* cx, AutoScriptVector& scripts,
-                                          LSprinter& graph);
-
-    /*
      * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
      * its data if we need to make a new Debugger.Frame.
      */
     bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
                                 const ScriptFrameIter* maybeIter, MutableHandleValue vp);
 
     inline Breakpoint* firstBreakpoint() const;
 
@@ -708,18 +692,16 @@ class Debugger : private mozilla::Linked
      * alternative path, containing its own call to onLeaveFrame.)
      */
     static inline bool onLeaveFrame(JSContext* cx, AbstractFramePtr frame, bool ok);
 
     static inline void onNewScript(JSContext* cx, HandleScript script);
     static inline void onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global);
     static inline bool onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame,
                                            int64_t when);
-    static inline bool observesIonCompilation(JSContext* cx);
-    static inline void onIonCompilation(JSContext* cx, AutoScriptVector& scripts, LSprinter& graph);
     static JSTrapStatus onTrap(JSContext* cx, MutableHandleValue vp);
     static JSTrapStatus onSingleStep(JSContext* cx, MutableHandleValue vp);
     static bool handleBaselineOsr(JSContext* cx, InterpreterFrame* from, jit::BaselineFrame* to);
     static bool handleIonBailout(JSContext* cx, jit::RematerializedFrame* from, jit::BaselineFrame* to);
     static void handleUnrecoverableIonBailoutError(JSContext* cx, jit::RematerializedFrame* frame);
     static void propagateForcedReturn(JSContext* cx, AbstractFramePtr frame, HandleValue rval);
     static bool hasLiveHook(GlobalObject* global, Hook which);
     static bool inFrameMaps(AbstractFramePtr frame);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1049,17 +1049,16 @@ HelperThread::handleAsmJSWorkload()
         AutoUnlockHelperThreadState unlock;
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(), asmData->runtime);
 
         jit::JitContext jcx(asmData->mir->compartment->runtime(),
                             asmData->mir->compartment,
                             &asmData->mir->alloc());
 
         int64_t before = PRMJ_Now();
-        jit::AutoSpewEndFunction spewEndFunction(asmData->mir);
 
         if (!OptimizeMIR(asmData->mir))
             break;
 
         asmData->lir = GenerateLIR(asmData->mir);
         if (!asmData->lir)
             break;
 
deleted file mode 100644
--- a/js/src/vm/Printer.cpp
+++ /dev/null
@@ -1,609 +0,0 @@
-/* -*- 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 "vm/Printer.h"
-
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-
-#include "jscntxt.h"
-#include "jsprf.h"
-#include "jsutil.h"
-
-#include "ds/LifoAlloc.h"
-
-namespace js {
-
-GenericPrinter::GenericPrinter()
-  : reportedOOM_(false)
-{
-}
-
-void
-GenericPrinter::reportOutOfMemory()
-{
-    if (reportedOOM_)
-        return;
-    reportedOOM_ = true;
-}
-
-bool
-GenericPrinter::hadOutOfMemory() const
-{
-    return reportedOOM_;
-}
-
-int
-GenericPrinter::put(const char* s)
-{
-    return put(s, strlen(s));
-}
-
-int
-GenericPrinter::printf(const char* fmt, ...)
-{
-    va_list va;
-    va_start(va, fmt);
-    int i = vprintf(fmt, va);
-    va_end(va);
-    return i;
-}
-
-int
-GenericPrinter::vprintf(const char* fmt, va_list ap)
-{
-    // Simple shortcut to avoid allocating strings.
-    if (strchr(fmt, '%') == nullptr)
-        return put(fmt);
-
-    char* bp;
-    bp = JS_vsmprintf(fmt, ap);      /* XXX vsaprintf */
-    if (!bp) {
-        reportOutOfMemory();
-        return -1;
-    }
-    int i = put(bp);
-    js_free(bp);
-    return i;
-}
-
-const size_t Sprinter::DefaultSize = 64;
-
-bool
-Sprinter::realloc_(size_t newSize)
-{
-    MOZ_ASSERT(newSize > (size_t) offset);
-    char* newBuf = (char*) js_realloc(base, newSize);
-    if (!newBuf) {
-        reportOutOfMemory();
-        return false;
-    }
-    base = newBuf;
-    size = newSize;
-    base[size - 1] = 0;
-    return true;
-}
-
-Sprinter::Sprinter(ExclusiveContext* cx)
-  : context(cx),
-#ifdef DEBUG
-    initialized(false),
-#endif
-    base(nullptr), size(0), offset(0)
-{ }
-
-Sprinter::~Sprinter()
-{
-#ifdef DEBUG
-    if (initialized)
-        checkInvariants();
-#endif
-    js_free(base);
-}
-
-bool
-Sprinter::init()
-{
-    MOZ_ASSERT(!initialized);
-    base = (char*) js_malloc(DefaultSize);
-    if (!base) {
-        reportOutOfMemory();
-        return false;
-    }
-#ifdef DEBUG
-    initialized = true;
-#endif
-    *base = 0;
-    size = DefaultSize;
-    base[size - 1] = 0;
-    return true;
-}
-
-void
-Sprinter::checkInvariants() const
-{
-    MOZ_ASSERT(initialized);
-    MOZ_ASSERT((size_t) offset < size);
-    MOZ_ASSERT(base[size - 1] == 0);
-}
-
-const char*
-Sprinter::string() const
-{
-    return base;
-}
-
-const char*
-Sprinter::stringEnd() const
-{
-    return base + offset;
-}
-
-char*
-Sprinter::stringAt(ptrdiff_t off) const
-{
-    MOZ_ASSERT(off >= 0 && (size_t) off < size);
-    return base + off;
-}
-
-char&
-Sprinter::operator[](size_t off)
-{
-    MOZ_ASSERT(off < size);
-    return *(base + off);
-}
-
-char*
-Sprinter::reserve(size_t len)
-{
-    InvariantChecker ic(this);
-
-    while (len + 1 > size - offset) { /* Include trailing \0 */
-        if (!realloc_(size * 2))
-            return nullptr;
-    }
-
-    char* sb = base + offset;
-    offset += len;
-    return sb;
-}
-
-int
-Sprinter::put(const char* s, size_t len)
-{
-    InvariantChecker ic(this);
-
-    const char* oldBase = base;
-    const char* oldEnd = base + size;
-
-    ptrdiff_t oldOffset = offset;
-    char* bp = reserve(len);
-    if (!bp)
-        return -1;
-
-    /* s is within the buffer already */
-    if (s >= oldBase && s < oldEnd) {
-        /* buffer was realloc'ed */
-        if (base != oldBase)
-            s = stringAt(s - oldBase);  /* this is where it lives now */
-        memmove(bp, s, len);
-    } else {
-        js_memcpy(bp, s, len);
-    }
-
-    bp[len] = 0;
-    return oldOffset;
-}
-
-int
-Sprinter::vprintf(const char* fmt, va_list ap)
-{
-    InvariantChecker ic(this);
-
-    do {
-        va_list aq;
-        va_copy(aq, ap);
-        int i = vsnprintf(base + offset, size - offset, fmt, aq);
-        va_end(aq);
-        if (i > -1 && (size_t) i < size - offset) {
-            offset += i;
-            return i;
-        }
-    } while (realloc_(size * 2));
-
-    return -1;
-}
-
-int
-Sprinter::putString(JSString* s)
-{
-    InvariantChecker ic(this);
-
-    size_t length = s->length();
-    size_t size = length;
-
-    ptrdiff_t oldOffset = offset;
-    char* buffer = reserve(size);
-    if (!buffer)
-        return -1;
-
-    JSLinearString* linear = s->ensureLinear(context);
-    if (!linear)
-        return -1;
-
-    JS::AutoCheckCannotGC nogc;
-    if (linear->hasLatin1Chars())
-        mozilla::PodCopy(reinterpret_cast<Latin1Char*>(buffer), linear->latin1Chars(nogc), length);
-    else
-        DeflateStringToBuffer(nullptr, linear->twoByteChars(nogc), length, buffer, &size);
-
-    buffer[size] = 0;
-    return oldOffset;
-}
-
-ptrdiff_t
-Sprinter::getOffset() const
-{
-    return offset;
-}
-
-void
-Sprinter::reportOutOfMemory()
-{
-    if (reportedOOM_)
-        return;
-    if (context)
-        ReportOutOfMemory(context);
-    reportedOOM_ = true;
-}
-
-ptrdiff_t
-Sprint(Sprinter* sp, const char* format, ...)
-{
-    va_list ap;
-    char* bp;
-    ptrdiff_t offset;
-
-    va_start(ap, format);
-    bp = JS_vsmprintf(format, ap);      /* XXX vsaprintf */
-    va_end(ap);
-    if (!bp) {
-        sp->reportOutOfMemory();
-        return -1;
-    }
-    offset = sp->put(bp);
-    js_free(bp);
-    return offset;
-}
-
-const char js_EscapeMap[] = {
-    '\b', 'b',
-    '\f', 'f',
-    '\n', 'n',
-    '\r', 'r',
-    '\t', 't',
-    '\v', 'v',
-    '"',  '"',
-    '\'', '\'',
-    '\\', '\\',
-    '\0'
-};
-
-template <typename CharT>
-static char*
-QuoteString(Sprinter* sp, const CharT* s, size_t length, char16_t quote)
-{
-    /* Sample off first for later return value pointer computation. */
-    ptrdiff_t offset = sp->getOffset();
-
-    if (quote && Sprint(sp, "%c", char(quote)) < 0)
-        return nullptr;
-
-    const CharT* end = s + length;
-
-    /* Loop control variables: end points at end of string sentinel. */
-    for (const CharT* t = s; t < end; s = ++t) {
-        /* Move t forward from s past un-quote-worthy characters. */
-        char16_t c = *t;
-        while (c < 127 && isprint(c) && c != quote && c != '\\' && c != '\t') {
-            c = *++t;
-            if (t == end)
-                break;
-        }
-
-        {
-            ptrdiff_t len = t - s;
-            ptrdiff_t base = sp->getOffset();
-            if (!sp->reserve(len))
-                return nullptr;
-
-            for (ptrdiff_t i = 0; i < len; ++i)
-                (*sp)[base + i] = char(*s++);
-            (*sp)[base + len] = 0;
-        }
-
-        if (t == end)
-            break;
-
-        /* Use js_EscapeMap, \u, or \x only if necessary. */
-        const char* escape;
-        if (!(c >> 8) && c != 0 && (escape = strchr(js_EscapeMap, int(c))) != nullptr) {
-            if (Sprint(sp, "\\%c", escape[1]) < 0)
-                return nullptr;
-        } else {
-            /*
-             * Use \x only if the high byte is 0 and we're in a quoted string,
-             * because ECMA-262 allows only \u, not \x, in Unicode identifiers
-             * (see bug 621814).
-             */
-            if (Sprint(sp, (quote && !(c >> 8)) ? "\\x%02X" : "\\u%04X", c) < 0)
-                return nullptr;
-        }
-    }
-
-    /* Sprint the closing quote and return the quoted string. */
-    if (quote && Sprint(sp, "%c", char(quote)) < 0)
-        return nullptr;
-
-    /*
-     * If we haven't Sprint'd anything yet, Sprint an empty string so that
-     * the return below gives a valid result.
-     */
-    if (offset == sp->getOffset() && Sprint(sp, "") < 0)
-        return nullptr;
-
-    return sp->stringAt(offset);
-}
-
-char*
-QuoteString(Sprinter* sp, JSString* str, char16_t quote)
-{
-    JSLinearString* linear = str->ensureLinear(sp->context);
-    if (!linear)
-        return nullptr;
-
-    JS::AutoCheckCannotGC nogc;
-    return linear->hasLatin1Chars()
-           ? QuoteString(sp, linear->latin1Chars(nogc), linear->length(), quote)
-           : QuoteString(sp, linear->twoByteChars(nogc), linear->length(), quote);
-}
-
-JSString*
-QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote)
-{
-    Sprinter sprinter(cx);
-    if (!sprinter.init())
-        return nullptr;
-    char* bytes = QuoteString(&sprinter, str, quote);
-    if (!bytes)
-        return nullptr;
-    return NewStringCopyZ<CanGC>(cx, bytes);
-}
-
-Fprinter::Fprinter(FILE* fp)
-  : file_(nullptr)
-{
-    init(fp);
-}
-
-Fprinter::Fprinter()
-  : file_(nullptr)
-{ }
-
-Fprinter::~Fprinter()
-{
-    MOZ_ASSERT_IF(init_, !file_);
-}
-
-bool
-Fprinter::init(const char* path)
-{
-    MOZ_ASSERT(!file_);
-    file_ = fopen(path, "w");
-    if (!file_)
-        return false;
-    init_ = true;
-    return true;
-}
-
-void
-Fprinter::init(FILE *fp)
-{
-    MOZ_ASSERT(!file_);
-    file_ = fp;
-    init_ = false;
-}
-
-void
-Fprinter::flush()
-{
-    MOZ_ASSERT(file_);
-    fflush(file_);
-}
-
-void
-Fprinter::finish()
-{
-    MOZ_ASSERT(file_);
-    if (init_)
-        fclose(file_);
-    file_ = nullptr;
-}
-
-int
-Fprinter::put(const char* s, size_t len)
-{
-    MOZ_ASSERT(file_);
-    int i = fwrite(s, len, 1, file_);
-    if (i == -1 || i != int(len))
-        reportOutOfMemory();
-    return i;
-}
-
-int
-Fprinter::put(const char* s)
-{
-    MOZ_ASSERT(file_);
-    int i = fputs(s, file_);
-    if (i == -1)
-        reportOutOfMemory();
-    return i;
-}
-
-int
-Fprinter::printf(const char* fmt, ...)
-{
-    MOZ_ASSERT(file_);
-    va_list ap;
-    va_start(ap, fmt);
-    int i = vfprintf(file_, fmt, ap);
-    if (i == -1)
-        reportOutOfMemory();
-    va_end(ap);
-    return i;
-}
-
-int
-Fprinter::vprintf(const char* fmt, va_list ap)
-{
-    MOZ_ASSERT(file_);
-    int i = vfprintf(file_, fmt, ap);
-    if (i == -1)
-        reportOutOfMemory();
-    return i;
-}
-
-LSprinter::LSprinter(LifoAlloc* lifoAlloc)
-  : alloc_(lifoAlloc),
-    head_(nullptr),
-    tail_(nullptr),
-    unused_(0)
-{ }
-
-LSprinter::~LSprinter()
-{
-    // This LSprinter might be allocated as part of the same LifoAlloc, so we
-    // should not expect the destructor to be called.
-}
-
-void
-LSprinter::exportInto(GenericPrinter& out) const
-{
-    if (!head_)
-        return;
-
-    for (Chunk* it = head_; it != tail_; it = it->next)
-        out.put(it->chars(), it->length);
-    out.put(tail_->chars(), tail_->length - unused_);
-}
-
-void
-LSprinter::clear()
-{
-    head_ = nullptr;
-    tail_ = nullptr;
-    unused_ = 0;
-    reportedOOM_ = false;
-}
-
-int
-LSprinter::put(const char* s, size_t len)
-{
-    size_t origLen = len;
-    if (unused_ > 0 && tail_) {
-        size_t minLen = unused_ < len ? unused_ : len;
-        js_memcpy(tail_->end() - unused_, s, minLen);
-        unused_ -= minLen;
-        len -= minLen;
-        s += minLen;
-    }
-
-    if (len == 0)
-        return origLen;
-
-    size_t allocLength = AlignBytes(sizeof(Chunk) + len, js::detail::LIFO_ALLOC_ALIGN);
-    Chunk* last = reinterpret_cast<Chunk*>(alloc_->alloc(allocLength));
-    if (!last) {
-        reportOutOfMemory();
-        return origLen - len;
-    }
-
-    if (tail_ && reinterpret_cast<char*>(last) == tail_->end()) {
-        // tail_ and last are next to each others in memory, knowing that the
-        // TempAlloctator has no meta data and is just a bump allocator, we
-        // append the new allocated space to the tail_.
-        unused_ = allocLength;
-        tail_->length += allocLength;
-    } else {
-        // Remove the size of the header from the allocated length.
-        allocLength -= sizeof(Chunk);
-        last->next = nullptr;
-        last->length = allocLength;
-        unused_ = allocLength;
-        if (!head_)
-            head_ = last;
-        else
-            tail_->next = last;
-
-        tail_ = last;
-    }
-
-    MOZ_ASSERT(tail_->length >= unused_);
-    js_memcpy(tail_->end() - unused_, s, len);
-    unused_ -= len;
-    return origLen;
-}
-
-int
-LSprinter::put(const char* s)
-{
-    return put(s, strlen(s));
-}
-
-int
-LSprinter::printf(const char* fmt, ...)
-{
-    va_list va;
-    va_start(va, fmt);
-    int i = vprintf(fmt, va);
-    va_end(va);
-    return i;
-}
-
-int
-LSprinter::vprintf(const char* fmt, va_list ap)
-{
-    // Simple shortcut to avoid allocating strings.
-    if (strchr(fmt, '%') == nullptr)
-        return put(fmt);
-
-    char* bp;
-    bp = JS_vsmprintf(fmt, ap);      /* XXX vsaprintf */
-    if (!bp) {
-        reportOutOfMemory();
-        return -1;
-    }
-    int i = put(bp);
-    js_free(bp);
-    return i;
-}
-
-void
-LSprinter::reportOutOfMemory()
-{
-    if (reportedOOM_)
-        return;
-    reportedOOM_ = true;
-}
-
-bool
-LSprinter::hadOutOfMemory() const
-{
-    return reportedOOM_;
-}
-
-} // namespace js
deleted file mode 100644
--- a/js/src/vm/Printer.h
+++ /dev/null
@@ -1,223 +0,0 @@
-/* -*- 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 vm_Printer_h
-#define vm_Printer_h
-
-#include <stdarg.h>
-#include <stdio.h>
-
-class JSString;
-
-namespace js {
-
-class ExclusiveContext;
-class LifoAlloc;
-
-// Generic printf interface, similar to an ostream in the standard library.
-//
-// This class is useful to make generic printers which can work either with a
-// file backend, with a buffer allocated with an ExclusiveContext or a link-list
-// of chunks allocated with a LifoAlloc.
-class GenericPrinter
-{
-  protected:
-    bool                    reportedOOM_;   // record reported OOM.
-
-    GenericPrinter();
-
-  public:
-    // Puts |len| characters from |s| at the current position and return an offset to
-    // the beginning of this new data.
-    virtual int put(const char* s, size_t len) = 0;
-    virtual int put(const char* s);
-
-    // Prints a formatted string into the buffer.
-    virtual int printf(const char* fmt, ...);
-    virtual int vprintf(const char* fmt, va_list ap);
-
-    // Report that a string operation failed to get the memory it requested. The
-    // first call to this function calls JS_ReportOutOfMemory, and sets this
-    // Sprinter's outOfMemory flag; subsequent calls do nothing.
-    virtual void reportOutOfMemory();
-
-    // Return true if this Sprinter ran out of memory.
-    virtual bool hadOutOfMemory() const;
-};
-
-// Sprintf, but with unlimited and automatically allocated buffering.
-class Sprinter final : public GenericPrinter
-{
-  public:
-    struct InvariantChecker
-    {
-        const Sprinter* parent;
-
-        explicit InvariantChecker(const Sprinter* p) : parent(p) {
-            parent->checkInvariants();
-        }
-
-        ~InvariantChecker() {
-            parent->checkInvariants();
-        }
-    };
-
-    ExclusiveContext*       context;        // context executing the decompiler
-
-  private:
-    static const size_t     DefaultSize;
-#ifdef DEBUG
-    bool                    initialized;    // true if this is initialized, use for debug builds
-#endif
-    char*                   base;           // malloc'd buffer address
-    size_t                  size;           // size of buffer allocated at base
-    ptrdiff_t               offset;         // offset of next free char in buffer
-
-    bool realloc_(size_t newSize);
-
-  public:
-    explicit Sprinter(ExclusiveContext* cx);
-    ~Sprinter();
-
-    // Initialize this sprinter, returns false on error.
-    bool init();
-
-    void checkInvariants() const;
-
-    const char* string() const;
-    const char* stringEnd() const;
-    // Returns the string at offset |off|.
-    char* stringAt(ptrdiff_t off) const;
-    // Returns the char at offset |off|.
-    char& operator[](size_t off);
-
-    // Attempt to reserve len + 1 space (for a trailing nullptr byte). If the
-    // attempt succeeds, return a pointer to the start of that space and adjust the
-    // internal content. The caller *must* completely fill this space on success.
-    char* reserve(size_t len);
-
-    // Puts |len| characters from |s| at the current position and return an offset to
-    // the beginning of this new data.
-    using GenericPrinter::put;
-    virtual int put(const char* s, size_t len) override;
-
-    // Prints a formatted string into the buffer.
-    virtual int vprintf(const char* fmt, va_list ap) override;
-
-    int putString(JSString* str);
-
-    ptrdiff_t getOffset() const;
-
-    // Report that a string operation failed to get the memory it requested. The
-    // first call to this function calls JS_ReportOutOfMemory, and sets this
-    // Sprinter's outOfMemory flag; subsequent calls do nothing.
-    virtual void reportOutOfMemory() override;
-};
-
-// Fprinter, print a string directly into a file.
-class Fprinter final : public GenericPrinter
-{
-  private:
-    FILE*                   file_;
-    bool                    init_;
-
-  public:
-    explicit Fprinter(FILE* fp);
-    Fprinter();
-    ~Fprinter();
-
-    // Initialize this printer, returns false on error.
-    bool init(const char* path);
-    void init(FILE* fp);
-    bool isInitialized() {
-        return file_ != nullptr;
-    }
-    void flush();
-    void finish();
-
-    // Puts |len| characters from |s| at the current position and return an
-    // offset to the beginning of this new data.
-    virtual int put(const char* s, size_t len) override;
-    virtual int put(const char* s) override;
-
-    // Prints a formatted string into the buffer.
-    virtual int printf(const char* fmt, ...) override;
-    virtual int vprintf(const char* fmt, va_list ap) override;
-};
-
-// LSprinter, is similar to Sprinter except that instead of using an
-// ExclusiveContext to allocate strings, it use a LifoAlloc as a backend for the
-// allocation of the chunk of the string.
-class LSprinter final : public GenericPrinter
-{
-  private:
-    struct Chunk
-    {
-        Chunk* next;
-        size_t length;
-
-        char* chars() {
-            return reinterpret_cast<char*>(this + 1);
-        }
-        char* end() {
-            return chars() + length;
-        }
-    };
-
-  private:
-    LifoAlloc*              alloc_;          // LifoAlloc used as a backend of chunk allocations.
-    Chunk*                  head_;
-    Chunk*                  tail_;
-    size_t                  unused_;
-
-  public:
-    explicit LSprinter(LifoAlloc* lifoAlloc);
-    ~LSprinter();
-
-    // Copy the content of the chunks into another printer, such that we can
-    // flush the content of this printer to a file.
-    void exportInto(GenericPrinter& out) const;
-
-    // Drop the current string, and let them be free with the LifoAlloc.
-    void clear();
-
-    // Puts |len| characters from |s| at the current position and return an
-    // offset to the beginning of this new data.
-    virtual int put(const char* s, size_t len) override;
-    virtual int put(const char* s) override;
-
-    // Prints a formatted string into the buffer.
-    virtual int printf(const char* fmt, ...) override;
-    virtual int vprintf(const char* fmt, va_list ap) override;
-
-    // Report that a string operation failed to get the memory it requested. The
-    // first call to this function calls JS_ReportOutOfMemory, and sets this
-    // Sprinter's outOfMemory flag; subsequent calls do nothing.
-    virtual void reportOutOfMemory() override;
-
-    // Return true if this Sprinter ran out of memory.
-    virtual bool hadOutOfMemory() const override;
-};
-
-extern ptrdiff_t
-Sprint(Sprinter* sp, const char* format, ...);
-
-// Map escaped code to the letter/symbol escaped with a backslash.
-extern const char       js_EscapeMap[];
-
-// Return a GC'ed string containing the chars in str, with any non-printing
-// chars or quotes (' or " as specified by the quote argument) escaped, and
-// with the quote character at the beginning and end of the result string.
-extern JSString*
-QuoteString(ExclusiveContext* cx, JSString* str, char16_t quote);
-
-extern char*
-QuoteString(Sprinter* sp, JSString* str, char16_t quote);
-
-
-} // namespace js
-
-#endif // vm_Printer_h