Bug 1349917 - One-pass deserialization from binary format draft
authorDavid Teller <dteller@mozilla.com>
Fri, 14 Apr 2017 15:07:29 +0200
changeset 562839 28c68f2e0b552cd5c522cf76642df86de7a3b94e
parent 561484 b8e8671817db436258a4354b21af56f72c3c9298
child 564583 392418046ecc448edeef0ab4c30df66fe5e463a5
push id54142
push userdteller@mozilla.com
push dateFri, 14 Apr 2017 13:08:52 +0000
bugs1349917
milestone55.0a1
Bug 1349917 - One-pass deserialization from binary format MozReview-Commit-ID: FmPNGug08Jk
js/src/frontend/BinaryAST.cpp
js/src/frontend/BinaryAST.h
js/src/frontend/Parser.cpp
--- a/js/src/frontend/BinaryAST.cpp
+++ b/js/src/frontend/BinaryAST.cpp
@@ -8,64 +8,58 @@
 #include "frontend/BinaryAST.h"
 #include "frontend/ParseNode.h"
 #include "jsatom.h"
 #include "jsopcode.h"
 
 namespace js {
 namespace frontend {
 
-/*
-MOZ_MUST_USE bool binSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& out, std::ostringstream& debug);
-MOZ_MUST_USE ParseNode* binParse(JSContext* cx, std::istringstream& in, std::ostringstream& debug);
-*/
 
+/**
+ * A marker for presence/absence.
+ */
 enum OptionValue {
     OptionIsHere = -1,
     OptionIsAbsent = -2,
 };
 
-// A (pretty long) header to simplify debugging.
+// A (pretty long) header/footer to simplify debugging.
+// Useful for prototyping, probably useless for a real product.
 const char HEADER[] = "I am a header.";
 const char FOOTER[] = "Behold the footer.";
 
 // List of names of ParseNodeKind, to simplify logging.
 const char* NAMES[] = {
 #define EMIT_SLOT(name) "PNK_" #name,
     FOR_EACH_PARSE_NODE_KIND(EMIT_SLOT)
 #undef EMIT_SLOT
     "PNK_LIMIT"
 };
 
 size_t totalByteLength(const StringTree::Concat* concat) {
-    size_t total = sizeof concat->children.length();
+    size_t total = sizeof concat->children.length()
+        + (sizeof StringTree::variantKind);
     for (const UniquePtr<StringTree>& item: concat->children) {
-        total += item->byteLength
-         + sizeof HEADER
-         + sizeof FOOTER
-         + sizeof item->variantKind
+        total
+         += sizeof HEADER
          + sizeof item->kind
-         + sizeof item->byteLength;
+         + sizeof item->byteLength
+         + item->byteLength
+         + sizeof FOOTER;
     }
     return total;
 }
 
-size_t totalByteLength(const StringTree::Leaf* concat) {
-    return concat->data.length();
+size_t totalByteLength(const StringTree::Leaf* leaf) {
+    return leaf->data.length()
+        + sizeof StringTree::variantKind;
 }
-/*
-size_t totalByteLength(const StringTree::Variant& variant) {
-    if (variant.is<StringTree::Leaf*>()) {
-        return totalByteLength(variant.as<StringTree::Leaf*>());
-    } else {
-        return totalByteLength(variant.as<StringTree::Concat*>());
-    }
-}
-*/
 
+// FIXME: We should rather have a StringTreeWriter.
 StringTree::~StringTree()
 {
    switch (variantKind) {
        case VariantKind::IsLeaf:
            delete data.leaf;
            return;
        case VariantKind::IsConcat:
            delete data.concat;
@@ -162,26 +156,27 @@ StringTree::read(JSContext* cx, std::ist
     }
 
     ParseNodeKind kind;
     in.read((char*)&kind, sizeof kind);
     if (kind >= mozilla::ArrayLength(NAMES)) {
         MOZ_CRASH("Bad kind");
     }
 
+    size_t byteLength;
+    in.read((char*)&byteLength, sizeof byteLength);
+
     VariantKind variant;
     in.read((char*)&variant, sizeof variant);
 
-    size_t byteLength;
-    in.read((char*)&byteLength, sizeof byteLength);
-
     MOZ_ASSERT(variant == VariantKind::IsLeaf || variant == VariantKind::IsConcat);
 
+    const size_t byteStart = in.tellg();
+
     UniquePtr<StringTree> result;
-    const size_t byteStart = in.tellg();
 
     if (variant == VariantKind::IsLeaf) {
         char* buf = new char[byteLength];
         in.read(buf, byteLength);
         std::string data(buf, byteLength);
         delete[] buf;
         result = UniquePtr<StringTree>(StringTree::makeLeaf(cx, kind, Move(data)));
     } else {
@@ -195,39 +190,41 @@ StringTree::read(JSContext* cx, std::ist
                 MOZ_CRASH();
                 return nullptr;
             }
         }
         result = UniquePtr<StringTree>(StringTree::makeConcat(cx, kind, Move(children)));
     }
     const size_t byteStop = in.tellg();
 
-    MOZ_ASSERT(byteStop - byteStart == byteLength);
+    MOZ_ASSERT(byteStop - byteStart == byteLength + sizeof byteLength);
 
     char footer[sizeof FOOTER + 1];
     in.read(footer, sizeof FOOTER);
     if (strncmp(footer, FOOTER, sizeof FOOTER) != 0) {
         MOZ_CRASH("Bad footer");
     }
 
     return result.release();
 }
 
 void
 StringTree::write(std::ostringstream& out) const {
     out.write(HEADER, sizeof HEADER);
     out.write((const char*)&kind, sizeof kind);
 
+    out.write((const char*)&byteLength, sizeof byteLength);
+    const size_t byteStart = out.tellp();
+
     out.write((const char*)&variantKind, sizeof variantKind);
 
-    out.write((const char*)&byteLength, sizeof byteLength);
-    const size_t byteStart = out.tellp();
     if (isLeaf()) {
         const Leaf& leaf = asLeaf();
         out << leaf.data;
+//        std::cerr << "writeLeaf() (" << byteLength << ") => " << leaf.data << "\n";
     } else {
         const Concat& concat = asConcat();
         const size_t length = concat.children.length();
         out.write((const char*)&length, sizeof length);
         for (const UniquePtr<StringTree>& tree: concat.children) {
             tree->write(out);
         }
     }
@@ -261,17 +258,17 @@ void serializeAtom(JSContext* cx, JSAtom
         OptionValue kind(OptionIsAbsent);
         out.write((const char*)&kind, sizeof kind);
     }
     result = Move(out.str());
 }
 
 JSAtom* deserializeAtom(JSContext* cx, const std::string& data)
 {
-    std::istringstream in(data);
+    std::istringstream in(data); // FIXME: Useless copy.
 
     OptionValue op;
     in.read((char*)&op, sizeof(op));
 
     if (op == OptionIsHere) {
         size_t size;
         in.read((char*)&size, sizeof(size));
 
@@ -285,55 +282,35 @@ JSAtom* deserializeAtom(JSContext* cx, c
 
         // FIXME: We don't need copy.
         JSAtom* result = js::Atomize(cx, buf, size);
         delete[] buf;
         return result;
     } else if (op == OptionIsAbsent) {
         return nullptr;
     }
-    MOZ_CRASH();
+    MOZ_CRASH("Invalid variant");
 }
 
 MOZ_MUST_USE
 bool deserializePropertyName(JSContext* cx, const std::string& data, MutableHandle<PropertyName*> label) {
     RootedAtom atom(cx, deserializeAtom(cx, data));
     if (atom) {
         label.set(atom->asPropertyName());
     } else {
         label.set(nullptr);
     }
     return true;
 }
 
-
-/*
-StringTree* treeSerializeAtom(JSContext* cx, JSAtom* atom) {
-    std::string data;
-    if (atom) {
-        OptionValue kind(OptionIsHere);
-        UniquePtr<JSString> quoted(QuoteString(cx, atom, 0));
-        JSAutoByteString bs;
-        const char* bytes = bs.encodeLatin1(cx, quoted.get());
-        data.append((const char*)kind, sizeof(kind));
-        data.append(bytes, strlen(bytes));
-    } else {
-        OptionValue kind(OptionIsAbsent);
-        data.append((const char*)kind, sizeof(kind));
-    }
-    StringTree::Leaf leaf(Move(data));
-    return cx->new_<StringTree>(Move(leaf));
-}
-*/
-
 MOZ_MUST_USE StringTree*
 treeSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& debug)
 {
     const ParseNodeKind kind = node ? node->getKind() : PNK_LIMIT;
-    // fprintf(stderr, "treeSerialize %s\n", NAMES[kind]);
+//    std::cerr << "treeSerialize " << NAMES[kind] << "\n";
     debug << NAMES[kind] << " ";
 
     switch (kind) {
         // Empty tree
         case PNK_LIMIT: {
             return StringTree::makeLeaf(cx, PNK_LIMIT);
         }
         // Body
@@ -533,17 +510,17 @@ treeSerialize(JSContext* cx, const Parse
             }
             return StringTree::makeConcat(cx, kind, Move(children));
         }
         case PNK_DOT: {
             std::string label;
             serializeAtom(cx, node->pn_atom, label);
 
             mozilla::Vector<UniquePtr<StringTree>> children;
-            if (!children.append(StringTree::makeLeaf(cx, kind, Move(label)))) {
+            if (!children.append(StringTree::makeLeaf(cx, PNK_DOT, Move(label)))) {
                 MOZ_CRASH();
             }
             if (!children.append(treeSerialize(cx, node->pn_expr, debug))) {
                 MOZ_CRASH();
             }
             return StringTree::makeConcat(cx, kind, Move(children));
         }
 
@@ -576,1064 +553,448 @@ treeSerialize(JSContext* cx, const Parse
         case PNK_POSHOLDER:
             return StringTree::makeLeaf(cx, kind);
 
         // Entirely undocumented nodes:
         case PNK_MODULE: MOZ_FALLTHROUGH;
         case PNK_DEBUGGER: MOZ_FALLTHROUGH;
         case PNK_ELISION: MOZ_FALLTHROUGH;
         case PNK_OBJECT_PROPERTY_NAME: {
-            // fprintf(stderr, "Placeholder %s\n", NAMES[kind]);
             return StringTree::makeLeaf(cx, kind);
         }
 
         // Stuff we don't handle yet:
         case PNK_REGEXP: {
             // fprintf(stderr, "Placeholder %s\n", NAMES[kind]);
             return StringTree::makeLeaf(cx, kind, "/Placeholder regexp/");
         }
     }
     MOZ_CRASH("Shouldn't reach that point.");
 }
 
-MOZ_MUST_USE ParseNode*
-treeParse(JSContext* cx, const StringTree* tree, std::ostringstream& debug)
-{
-    const ParseNodeKind kind = tree->kind;
-
-    debug << NAMES[kind] << " ";
-    // fprintf(stderr, "treeParse %s\n", NAMES[kind]);
+class TreeParser MOZ_RAII {
+public:
+    TreeParser(JSContext* cx_, std::istringstream& in_, std::ostringstream& debug_)
+        : cx(cx_)
+        , in(in_)
+        , debug(debug_)
+    { }
+    ~TreeParser() {
+        MOZ_ASSERT(in.peek() == std::char_traits<char>::eof());
+    }
 
-    switch (kind) {
-        // Empty subtree.
-        case PNK_LIMIT: {
-            const StringTree::Leaf& leaf = tree->asLeaf();
-            MOZ_ASSERT(leaf.data.length() == 0);
-            return nullptr;
-        }
-
-        case PNK_LEXICALSCOPE: {
-            const StringTree::Concat& concat = tree->asConcat();
-            MOZ_ASSERT(concat.children.length() == 1);
-
-            UniquePtr<ParseNode> body(treeParse(cx, concat.children[0].get(), debug));
-            MOZ_ASSERT(body);
-
-            return cx->new_<LexicalScopeNode>(nullptr, body.release());
-        }
+    // Parse a subtree as a string. The subtree must have been serialized as a string.
+    // FIXME: Crappy documentation suggests crappy concepts. Clean this up.
+    void parseBuf(ParseNodeKind& kind, std::string& buf)
+    {
+        size_t byteLength;
+        readHeader(kind, byteLength);
 
-        case PNK_FUNCTION: {
-            const StringTree::Concat& concat = tree->asConcat();
-            MOZ_ASSERT(concat.children.length() == 1);
-
-            UniquePtr<ParseNode> body(treeParse(cx, concat.children[0].get(), debug));
-            MOZ_ASSERT(body);
-
-            CodeNode* result(cx->new_<CodeNode>(kind, JSOP_NOP, TokenPos()));
-            result->pn_body = body.release();
-            return result;
-        }
+        const size_t byteStart = in.tellg();
+        readNodeAsLeaf(buf);
+        readFooter(byteStart, byteLength);
+    }
 
-        case PNK_IF: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC: MOZ_FALLTHROUGH;
-        case PNK_EXPORT: MOZ_FALLTHROUGH;
-        case PNK_FORHEAD:  MOZ_FALLTHROUGH;
-        case PNK_CONDITIONAL: MOZ_FALLTHROUGH;
-        case PNK_CLASS: MOZ_FALLTHROUGH;
-        case PNK_TRY: MOZ_FALLTHROUGH;
-        case PNK_CATCH: {
-            const StringTree::Concat& concat = tree->asConcat();
-            MOZ_ASSERT(concat.children.length() == 3);
+    // Parse a subtree as a ParseNode.
+    MOZ_MUST_USE ParseNode* parseNode()
+    {
+        ParseNodeKind kind;
+        size_t byteLength;
+        readHeader(kind, byteLength);
 
-            UniquePtr<ParseNode> kid1(treeParse(cx, concat.children[0].get(), debug));
-            UniquePtr<ParseNode> kid2(treeParse(cx, concat.children[1].get(), debug));
-            UniquePtr<ParseNode> kid3(treeParse(cx, concat.children[2].get(), debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, kid1.release(), kid2.release(), kid3.release(), TokenPos());
-        }
+        const size_t byteStart = in.tellg();
+
+        // Actually parse subtree.
+        UniquePtr<ParseNode> result(parseNode(kind));
+
+        readFooter(byteStart, byteLength);
+        return result.release();
+    }
 
-        case PNK_CASE: MOZ_FALLTHROUGH;
-        case PNK_WHILE: MOZ_FALLTHROUGH;
-        case PNK_DOWHILE: MOZ_FALLTHROUGH;
-        case PNK_FOR: MOZ_FALLTHROUGH;
-        case PNK_COMPREHENSIONFOR: MOZ_FALLTHROUGH;
-        case PNK_WITH: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_FROM: MOZ_FALLTHROUGH;
-        case PNK_ASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ADDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_SUBASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITXORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITANDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_LSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_RSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_URSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MULASSIGN: MOZ_FALLTHROUGH;
-        case PNK_DIVASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MODASSIGN: MOZ_FALLTHROUGH;
-        case PNK_POWASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ELEM: MOZ_FALLTHROUGH;
-        case PNK_COLON: MOZ_FALLTHROUGH;
-        case PNK_SHORTHAND: MOZ_FALLTHROUGH;
-        case PNK_SETTHIS: MOZ_FALLTHROUGH;
-        case PNK_SWITCH: MOZ_FALLTHROUGH;
-        case PNK_IMPORT: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC: MOZ_FALLTHROUGH;
-        case PNK_CLASSMETHOD: MOZ_FALLTHROUGH;
-        case PNK_CLASSNAMES: MOZ_FALLTHROUGH;
-        case PNK_NEWTARGET: {
-            // FIXME: Handle JSOP
-            const StringTree::Concat& concat = tree->asConcat();
-            MOZ_ASSERT(concat.children.length() == 2);
+    void readNodeAsLeaf(std::string& buf)
+    {
+        size_t byteLength;
+        in.read((char*)&byteLength, sizeof byteLength);
+
+        StringTree::VariantKind variant;
+        in.read((char*)&variant, sizeof variant);
+        MOZ_ASSERT(variant == StringTree::VariantKind::IsLeaf);
 
-            UniquePtr<ParseNode> left(treeParse(cx, concat.children[0].get(), debug));
-            UniquePtr<ParseNode> right(treeParse(cx, concat.children[1].get(), debug));
+        const size_t bufLength = byteLength - sizeof variant;
+        buf.resize(bufLength);
+        in.read(&buf[0], bufLength);
+
+//        std::cerr << "readNodeAsLeaf: (" << bufLength << ")" << buf << "\n";
+    }
+    size_t readNodeAsConcat() {
+        size_t byteLength;
+        in.read((char*)&byteLength, sizeof byteLength);
 
-            if (kind == PNK_CASE) {
-                return cx->new_<CaseClause>(left.release(), right.release(), 0);
-            } else {
-                return cx->new_<BinaryNode>(kind, JSOP_NOP, TokenPos(), left.release(), right.release());
-            }
-        }
-        case PNK_FORIN: MOZ_FALLTHROUGH;
-        case PNK_FOROF: {
-            const StringTree::Concat& concat = tree->asConcat();
-            MOZ_ASSERT(concat.children.length() == 2);
+        StringTree::VariantKind variant;
+        in.read((char*)&variant, sizeof variant);
+        MOZ_ASSERT(variant == StringTree::VariantKind::IsConcat);
 
-            UniquePtr<ParseNode> left(treeParse(cx, concat.children[0].get(), debug));
-            UniquePtr<ParseNode> right(treeParse(cx, concat.children[1].get(), debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, left.release(), nullptr, right.release());
-        }
+        size_t length;
+        in.read((char*)&length, sizeof length);
 
-        // pn_kid
-        case PNK_THROW: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_DEFAULT: MOZ_FALLTHROUGH;
-        case PNK_COMPUTED_NAME: MOZ_FALLTHROUGH;
-        case PNK_ARRAYPUSH: MOZ_FALLTHROUGH;
-        case PNK_SPREAD: MOZ_FALLTHROUGH;
-        case PNK_RETURN: MOZ_FALLTHROUGH;
-        case PNK_SEMI: MOZ_FALLTHROUGH;
-        case PNK_THIS: MOZ_FALLTHROUGH;
-        case PNK_YIELD: MOZ_FALLTHROUGH;
-        case PNK_YIELD_STAR: MOZ_FALLTHROUGH;
-        case PNK_AWAIT: MOZ_FALLTHROUGH;
-        case PNK_SUPERBASE: MOZ_FALLTHROUGH;
-        case PNK_INITIALYIELD: MOZ_FALLTHROUGH;
-        case PNK_POS: MOZ_FALLTHROUGH;
-        case PNK_NEG: MOZ_FALLTHROUGH;
-        case PNK_VOID: MOZ_FALLTHROUGH;
-        case PNK_NOT: MOZ_FALLTHROUGH;
-        case PNK_BITNOT: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFNAME: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFEXPR: MOZ_FALLTHROUGH;
-        case PNK_PREINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_PREDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_DELETENAME: MOZ_FALLTHROUGH;
-        case PNK_DELETEPROP: MOZ_FALLTHROUGH;
-        case PNK_DELETEELEM: MOZ_FALLTHROUGH;
-        case PNK_DELETEEXPR: MOZ_FALLTHROUGH;
-        case PNK_MUTATEPROTO: {
-            const mozilla::Vector<UniquePtr<StringTree>>& children = tree->children();
-            MOZ_ASSERT(children.length() == 1);
+        return length;
+    }
+private:
+    JSContext* cx;
+    std::istringstream& in;
+    std::ostringstream& debug;
 
-            return cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), treeParse(cx, children[0].get(), debug));
+    void readHeader(ParseNodeKind& kind, size_t& byteLength)
+    {
+        std::string header(sizeof HEADER, '\0');
+        in.read(&header[0], sizeof HEADER);
+        if (header.compare(0, sizeof HEADER - 1, HEADER) != 0) {
+            MOZ_CRASH("Bad header");
         }
 
-        // Linked lists
-        case PNK_CLASSMETHODLIST: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_STATEMENTLIST: MOZ_FALLTHROUGH;
-        case PNK_CATCHLIST: MOZ_FALLTHROUGH;
-        case PNK_VAR: MOZ_FALLTHROUGH;
-        case PNK_LET: MOZ_FALLTHROUGH;
-        case PNK_CONST: MOZ_FALLTHROUGH;
-        case PNK_COMMA: MOZ_FALLTHROUGH;
-        case PNK_CALL: MOZ_FALLTHROUGH;
-        case PNK_GENEXP: MOZ_FALLTHROUGH;
-        case PNK_ARRAY: MOZ_FALLTHROUGH;
-        case PNK_OBJECT: MOZ_FALLTHROUGH;
-        case PNK_PARAMSBODY: MOZ_FALLTHROUGH;
-        case PNK_OR: MOZ_FALLTHROUGH;
-        case PNK_AND: MOZ_FALLTHROUGH;
-        case PNK_BITOR: MOZ_FALLTHROUGH;
-        case PNK_BITXOR: MOZ_FALLTHROUGH;
-        case PNK_BITAND: MOZ_FALLTHROUGH;
-        case PNK_EQ: MOZ_FALLTHROUGH;
-        case PNK_NE: MOZ_FALLTHROUGH;
-        case PNK_STRICTEQ: MOZ_FALLTHROUGH;
-        case PNK_STRICTNE: MOZ_FALLTHROUGH;
-        case PNK_LT: MOZ_FALLTHROUGH;
-        case PNK_LE: MOZ_FALLTHROUGH;
-        case PNK_GT: MOZ_FALLTHROUGH;
-        case PNK_GE: MOZ_FALLTHROUGH;
-        case PNK_LSH: MOZ_FALLTHROUGH;
-        case PNK_RSH: MOZ_FALLTHROUGH;
-        case PNK_URSH: MOZ_FALLTHROUGH;
-        case PNK_ADD: MOZ_FALLTHROUGH;
-        case PNK_SUB: MOZ_FALLTHROUGH;
-        case PNK_STAR: MOZ_FALLTHROUGH;
-        case PNK_DIV: MOZ_FALLTHROUGH;
-        case PNK_MOD: MOZ_FALLTHROUGH;
-        case PNK_POW: MOZ_FALLTHROUGH;
-        case PNK_NEW: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING_LIST: MOZ_FALLTHROUGH;
-        case PNK_TAGGED_TEMPLATE: MOZ_FALLTHROUGH;
-        case PNK_CALLSITEOBJ: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_SUPERCALL: MOZ_FALLTHROUGH;
-        case PNK_ARRAYCOMP: MOZ_FALLTHROUGH;
-        case PNK_INSTANCEOF: MOZ_FALLTHROUGH;
-        case PNK_IN: {
-            const mozilla::Vector<UniquePtr<StringTree>>& children = tree->children();
-            ParseNode* latest = nullptr;
-
-            UniquePtr<ListNode> result(cx->new_<ListNode>(kind, TokenPos()));
-
-            for (uint32_t i = 0; i < children.length(); ++i) {
-                ParseNode* child = treeParse(cx, children[i].get(), debug);
-                if (i == 0) {
-                    result->pn_head = child;
-                    result->pn_tail = &result->pn_head;
-                } else {
-                    latest->pn_next = child;
-                    result->pn_tail  = &latest->pn_next;
-                }
-                latest = child;
-                result->pn_count++; // Incrementing progressively in case it helps if the destructor is called early.
-            }
-
-            return result.release();
+        in.read((char*)&kind, sizeof kind);
+        if (kind >= mozilla::ArrayLength(NAMES)) {
+            MOZ_CRASH("Bad kind");
         }
 
-        // Labels
-        case PNK_BREAK: MOZ_FALLTHROUGH;
-        case PNK_CONTINUE: {
-            auto leaf = tree->asLeaf();
-            RootedPropertyName label(cx);
-            if (!deserializePropertyName(cx, leaf.data, &label)) {
-                label = nullptr;
-            }
-            if (kind == PNK_BREAK) {
-                return cx->new_<BreakStatement>(label, TokenPos());
-            } else {
-                return cx->new_<ContinueStatement>(label, TokenPos());
-            }
-        }
-
-        case PNK_LABEL: MOZ_FALLTHROUGH;
-        case PNK_DOT: {
-            const mozilla::Vector<UniquePtr<StringTree>>& children = tree->children();
-            MOZ_ASSERT(children.length() == 2);
-
-            auto atom = children[0]->asLeaf();
-            RootedPropertyName label(cx);
-            if (!deserializePropertyName(cx, atom.data, &label)) {
-                label = nullptr;
-            }
-
-            UniquePtr<ParseNode> expr(treeParse(cx, children[1].get(), debug));
-	    if (kind == PNK_LABEL) {
-	      return cx->new_<LabeledStatement>(label, expr.release(), 0);
-	    } else {
-	      return cx->new_<PropertyAccess>(expr.release(), label, 0, 0);
-	    }
-        }
-
-        // Strings
-        case PNK_NAME: MOZ_FALLTHROUGH;
-        case PNK_STRING: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING: {
-            auto leaf = tree->asLeaf();
-            RootedAtom atom(cx, deserializeAtom(cx, leaf.data));
-            return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos(), atom);
-        }
+        // Lookahead bytelength
+        in.read((char*)&byteLength, sizeof byteLength);
+        in.seekg(-sizeof byteLength, std::ios_base::seekdir::cur);
+    }
 
-        case PNK_NUMBER: {
-            auto leaf = tree->asLeaf();
-            std::istringstream input(leaf.data);
-
-            double dval;
-            input.read((char*)&dval, sizeof dval);
-
-            DecimalPoint point;
-            input.read((char*)&point, sizeof point);
-
-            UniquePtr<ParseNode> node(cx->new_<ParseNode>(kind, JSOP_NOP, PN_NULLARY));
-            node->initNumber(dval, point);
-            return node.release();
-        }
+    void readFooter(const size_t byteStart, const size_t byteLength) {
+        // Check byteLength, footer
+        const size_t byteStop = in.tellg();
 
-        // Nullaries
-        case PNK_TRUE: MOZ_FALLTHROUGH;
-        case PNK_FALSE: MOZ_FALLTHROUGH;
-        case PNK_NULL: MOZ_FALLTHROUGH;
-        case PNK_RAW_UNDEFINED: MOZ_FALLTHROUGH;
-        case PNK_GENERATOR: MOZ_FALLTHROUGH;
-        case PNK_NOP: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_BATCH_SPEC: MOZ_FALLTHROUGH;
-        case PNK_POSHOLDER: {
-            auto leaf = tree->asLeaf();
-            MOZ_ASSERT(leaf.data.length() == 0);
+        MOZ_ASSERT(byteStop - byteStart == byteLength + sizeof byteLength);
 
-            return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
-        }
-
-        // Entirely undocumented nodes:
-        case PNK_MODULE: MOZ_FALLTHROUGH;
-        case PNK_DEBUGGER: MOZ_FALLTHROUGH;
-        case PNK_ELISION: MOZ_FALLTHROUGH;
-        case PNK_OBJECT_PROPERTY_NAME: {
-            // fprintf(stderr, "Placeholder %s\n", NAMES[kind]);
-            return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
-        }
-
-        // Stuff we don't handle yet:
-        case PNK_REGEXP: {
-            // fprintf(stderr, "Placeholder %s\n", NAMES[kind]);
-            return cx->new_<RegExpLiteral>(nullptr, TokenPos());
+        std::string footer(sizeof FOOTER, '\0');
+        in.read(&footer[0], sizeof FOOTER);
+        if (footer.compare(0, sizeof FOOTER - 1, FOOTER) != 0) {
+            MOZ_CRASH("Bad footer");
         }
     }
 
-    MOZ_CRASH("treeParse: out of switch()");
-    return nullptr;
-}
+    MOZ_MUST_USE ParseNode* parseNode(ParseNodeKind kind)
+    {
+//        std::cerr << "treeParse " << NAMES[kind] << "\n";
+        debug << NAMES[kind] << " ";
+
+        switch (kind) {
+            // Empty subtree.
+            case PNK_LIMIT: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                MOZ_ASSERT(leaf.length() == 0);
+                return nullptr;
+            }
+
+            case PNK_LEXICALSCOPE: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 1);
+
+                UniquePtr<ParseNode> body(parseNode());
+                MOZ_ASSERT(body);
+
+                return cx->new_<LexicalScopeNode>(nullptr, body.release());
+            }
+
+            case PNK_FUNCTION: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 1);
+
+                UniquePtr<ParseNode> body(parseNode());
+                MOZ_ASSERT(body);
+
+                CodeNode* result(cx->new_<CodeNode>(kind, JSOP_NOP, TokenPos()));
+                result->pn_body = body.release();
+                return result;
+            }
+
+            case PNK_IF: MOZ_FALLTHROUGH;
+            case PNK_IMPORT_SPEC: MOZ_FALLTHROUGH;
+            case PNK_EXPORT: MOZ_FALLTHROUGH;
+            case PNK_FORHEAD:  MOZ_FALLTHROUGH;
+            case PNK_CONDITIONAL: MOZ_FALLTHROUGH;
+            case PNK_CLASS: MOZ_FALLTHROUGH;
+            case PNK_TRY: MOZ_FALLTHROUGH;
+            case PNK_CATCH: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 3);
+
+                UniquePtr<ParseNode> kid1(parseNode());
+                UniquePtr<ParseNode> kid2(parseNode());
+                UniquePtr<ParseNode> kid3(parseNode());
+                return cx->new_<TernaryNode>(kind, JSOP_NOP, kid1.release(), kid2.release(), kid3.release(), TokenPos());
+            }
 
-#if DO_NOT_BUILD
-MOZ_MUST_USE bool binSerializeAtom(JSContext* cx, JSAtom* atom, std::ostringstream& out) {
-    if (atom) {
-        OptionValue kind(OptionIsHere);
-        out.write((const char*)&kind, sizeof(kind));
+            case PNK_CASE: MOZ_FALLTHROUGH;
+            case PNK_WHILE: MOZ_FALLTHROUGH;
+            case PNK_DOWHILE: MOZ_FALLTHROUGH;
+            case PNK_FOR: MOZ_FALLTHROUGH;
+            case PNK_COMPREHENSIONFOR: MOZ_FALLTHROUGH;
+            case PNK_WITH: MOZ_FALLTHROUGH;
+            case PNK_EXPORT_FROM: MOZ_FALLTHROUGH;
+            case PNK_ASSIGN: MOZ_FALLTHROUGH;
+            case PNK_ADDASSIGN: MOZ_FALLTHROUGH;
+            case PNK_SUBASSIGN: MOZ_FALLTHROUGH;
+            case PNK_BITORASSIGN: MOZ_FALLTHROUGH;
+            case PNK_BITXORASSIGN: MOZ_FALLTHROUGH;
+            case PNK_BITANDASSIGN: MOZ_FALLTHROUGH;
+            case PNK_LSHASSIGN: MOZ_FALLTHROUGH;
+            case PNK_RSHASSIGN: MOZ_FALLTHROUGH;
+            case PNK_URSHASSIGN: MOZ_FALLTHROUGH;
+            case PNK_MULASSIGN: MOZ_FALLTHROUGH;
+            case PNK_DIVASSIGN: MOZ_FALLTHROUGH;
+            case PNK_MODASSIGN: MOZ_FALLTHROUGH;
+            case PNK_POWASSIGN: MOZ_FALLTHROUGH;
+            case PNK_ELEM: MOZ_FALLTHROUGH;
+            case PNK_COLON: MOZ_FALLTHROUGH;
+            case PNK_SHORTHAND: MOZ_FALLTHROUGH;
+            case PNK_SETTHIS: MOZ_FALLTHROUGH;
+            case PNK_SWITCH: MOZ_FALLTHROUGH;
+            case PNK_IMPORT: MOZ_FALLTHROUGH;
+            case PNK_EXPORT_SPEC: MOZ_FALLTHROUGH;
+            case PNK_CLASSMETHOD: MOZ_FALLTHROUGH;
+            case PNK_CLASSNAMES: MOZ_FALLTHROUGH;
+            case PNK_NEWTARGET: MOZ_FALLTHROUGH;
+            case PNK_FORIN: MOZ_FALLTHROUGH;
+            case PNK_FOROF: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 2);
+
+                UniquePtr<ParseNode> left(parseNode());
+                UniquePtr<ParseNode> right(parseNode());
 
-        JSString* quoted = QuoteString(cx, atom, 0);
-        JSAutoByteString bs;
-        const char* bytes = bs.encodeLatin1(cx, quoted);
+                if (kind == PNK_CASE) {
+                    return cx->new_<CaseClause>(left.release(), right.release(), 0);
+                } else if (kind == PNK_FORIN || kind == PNK_FOROF) {
+                    return cx->new_<TernaryNode>(kind, JSOP_NOP, left.release(), nullptr, right.release());
+                } else {
+                    return cx->new_<BinaryNode>(kind, JSOP_NOP, TokenPos(), left.release(), right.release());
+                }
+            }
+
+            // pn_kid
+            case PNK_THROW: MOZ_FALLTHROUGH;
+            case PNK_EXPORT_DEFAULT: MOZ_FALLTHROUGH;
+            case PNK_COMPUTED_NAME: MOZ_FALLTHROUGH;
+            case PNK_ARRAYPUSH: MOZ_FALLTHROUGH;
+            case PNK_SPREAD: MOZ_FALLTHROUGH;
+            case PNK_RETURN: MOZ_FALLTHROUGH;
+            case PNK_SEMI: MOZ_FALLTHROUGH;
+            case PNK_THIS: MOZ_FALLTHROUGH;
+            case PNK_YIELD: MOZ_FALLTHROUGH;
+            case PNK_YIELD_STAR: MOZ_FALLTHROUGH;
+            case PNK_AWAIT: MOZ_FALLTHROUGH;
+            case PNK_SUPERBASE: MOZ_FALLTHROUGH;
+            case PNK_INITIALYIELD: MOZ_FALLTHROUGH;
+            case PNK_POS: MOZ_FALLTHROUGH;
+            case PNK_NEG: MOZ_FALLTHROUGH;
+            case PNK_VOID: MOZ_FALLTHROUGH;
+            case PNK_NOT: MOZ_FALLTHROUGH;
+            case PNK_BITNOT: MOZ_FALLTHROUGH;
+            case PNK_TYPEOFNAME: MOZ_FALLTHROUGH;
+            case PNK_TYPEOFEXPR: MOZ_FALLTHROUGH;
+            case PNK_PREINCREMENT: MOZ_FALLTHROUGH;
+            case PNK_POSTINCREMENT: MOZ_FALLTHROUGH;
+            case PNK_PREDECREMENT: MOZ_FALLTHROUGH;
+            case PNK_POSTDECREMENT: MOZ_FALLTHROUGH;
+            case PNK_DELETENAME: MOZ_FALLTHROUGH;
+            case PNK_DELETEPROP: MOZ_FALLTHROUGH;
+            case PNK_DELETEELEM: MOZ_FALLTHROUGH;
+            case PNK_DELETEEXPR: MOZ_FALLTHROUGH;
+            case PNK_MUTATEPROTO: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 1);
+
+                return cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), parseNode());
+            }
 
-        const size_t size = strlen(bytes); // FIXME: Is this really the size?
-        out.write((const char*)&size, sizeof(size));
-        out.write(bytes, size);
+            // Linked lists
+            case PNK_CLASSMETHODLIST: MOZ_FALLTHROUGH;
+            case PNK_EXPORT_SPEC_LIST: MOZ_FALLTHROUGH;
+            case PNK_STATEMENTLIST: MOZ_FALLTHROUGH;
+            case PNK_CATCHLIST: MOZ_FALLTHROUGH;
+            case PNK_VAR: MOZ_FALLTHROUGH;
+            case PNK_LET: MOZ_FALLTHROUGH;
+            case PNK_CONST: MOZ_FALLTHROUGH;
+            case PNK_COMMA: MOZ_FALLTHROUGH;
+            case PNK_CALL: MOZ_FALLTHROUGH;
+            case PNK_GENEXP: MOZ_FALLTHROUGH;
+            case PNK_ARRAY: MOZ_FALLTHROUGH;
+            case PNK_OBJECT: MOZ_FALLTHROUGH;
+            case PNK_PARAMSBODY: MOZ_FALLTHROUGH;
+            case PNK_OR: MOZ_FALLTHROUGH;
+            case PNK_AND: MOZ_FALLTHROUGH;
+            case PNK_BITOR: MOZ_FALLTHROUGH;
+            case PNK_BITXOR: MOZ_FALLTHROUGH;
+            case PNK_BITAND: MOZ_FALLTHROUGH;
+            case PNK_EQ: MOZ_FALLTHROUGH;
+            case PNK_NE: MOZ_FALLTHROUGH;
+            case PNK_STRICTEQ: MOZ_FALLTHROUGH;
+            case PNK_STRICTNE: MOZ_FALLTHROUGH;
+            case PNK_LT: MOZ_FALLTHROUGH;
+            case PNK_LE: MOZ_FALLTHROUGH;
+            case PNK_GT: MOZ_FALLTHROUGH;
+            case PNK_GE: MOZ_FALLTHROUGH;
+            case PNK_LSH: MOZ_FALLTHROUGH;
+            case PNK_RSH: MOZ_FALLTHROUGH;
+            case PNK_URSH: MOZ_FALLTHROUGH;
+            case PNK_ADD: MOZ_FALLTHROUGH;
+            case PNK_SUB: MOZ_FALLTHROUGH;
+            case PNK_STAR: MOZ_FALLTHROUGH;
+            case PNK_DIV: MOZ_FALLTHROUGH;
+            case PNK_MOD: MOZ_FALLTHROUGH;
+            case PNK_POW: MOZ_FALLTHROUGH;
+            case PNK_NEW: MOZ_FALLTHROUGH;
+            case PNK_TEMPLATE_STRING_LIST: MOZ_FALLTHROUGH;
+            case PNK_TAGGED_TEMPLATE: MOZ_FALLTHROUGH;
+            case PNK_CALLSITEOBJ: MOZ_FALLTHROUGH;
+            case PNK_IMPORT_SPEC_LIST: MOZ_FALLTHROUGH;
+            case PNK_SUPERCALL: MOZ_FALLTHROUGH;
+            case PNK_ARRAYCOMP: MOZ_FALLTHROUGH;
+            case PNK_INSTANCEOF: MOZ_FALLTHROUGH;
+            case PNK_IN: {
+                const size_t length = readNodeAsConcat();
+                ParseNode* latest = nullptr;
+
+                UniquePtr<ListNode> result(cx->new_<ListNode>(kind, TokenPos()));
+
+                for (uint32_t i = 0; i < length; ++i) {
+                    auto link = parseNode();
+                    if (i == 0) {
+                        result->pn_head = link;
+                        result->pn_tail = &result->pn_head;
+                    } else {
+                        latest->pn_next = link;
+                        result->pn_tail  = &latest->pn_next;
+                    }
+                    latest = link;
+                    result->pn_count++; // Incrementing progressively in case it helps if the destructor is called early.
+                }
+
+                return result.release();
+            }
+
+            // Labels
+            case PNK_BREAK: MOZ_FALLTHROUGH;
+            case PNK_CONTINUE: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                RootedPropertyName label(cx);
+                if (!deserializePropertyName(cx, leaf, &label)) {
+                    label = nullptr;
+                }
+                if (kind == PNK_BREAK) {
+                    return cx->new_<BreakStatement>(label, TokenPos());
+                } else {
+                    return cx->new_<ContinueStatement>(label, TokenPos());
+                }
+            }
 
-        // fprintf(stderr, "binSerializeAtom %s (%zu)\n", bytes, size);
-        return true;
-    } else {
-        OptionValue kind(OptionIsAbsent);
-        out.write((const char*)&kind, sizeof(kind));
-        return true;
+            case PNK_LABEL: MOZ_FALLTHROUGH;
+            case PNK_DOT: {
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 2);
+
+                ParseNodeKind subkind;
+                std::string leaf;
+                parseBuf(subkind, leaf);
+                MOZ_ASSERT(subkind == kind);
+                RootedAtom atom(cx, deserializeAtom(cx, leaf));
+
+                RootedPropertyName label(cx);
+                if (atom) {
+                    label.set(atom->asPropertyName());
+                }
+                UniquePtr<ParseNode> expr(parseNode());
+                if (kind == PNK_LABEL) {
+                  return cx->new_<LabeledStatement>(label, expr.release(), 0);
+                } else {
+                  return cx->new_<PropertyAccess>(expr.release(), label, 0, 0);
+                }
+            }
+
+            // Strings
+            case PNK_NAME: MOZ_FALLTHROUGH;
+            case PNK_STRING: MOZ_FALLTHROUGH;
+            case PNK_TEMPLATE_STRING: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                RootedAtom atom(cx, deserializeAtom(cx, leaf));
+                return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos(), atom);
+            }
+
+            case PNK_NUMBER: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                std::istringstream input(leaf); // FIXME: Useless copy.
+
+                double dval;
+                input.read((char*)&dval, sizeof dval);
+
+                DecimalPoint point;
+                input.read((char*)&point, sizeof point);
+
+                UniquePtr<ParseNode> node(cx->new_<ParseNode>(kind, JSOP_NOP, PN_NULLARY));
+                node->initNumber(dval, point);
+                return node.release();
+            }
+
+            // Nullaries
+            case PNK_TRUE: MOZ_FALLTHROUGH;
+            case PNK_FALSE: MOZ_FALLTHROUGH;
+            case PNK_NULL: MOZ_FALLTHROUGH;
+            case PNK_RAW_UNDEFINED: MOZ_FALLTHROUGH;
+            case PNK_GENERATOR: MOZ_FALLTHROUGH;
+            case PNK_NOP: MOZ_FALLTHROUGH;
+            case PNK_EXPORT_BATCH_SPEC: MOZ_FALLTHROUGH;
+            case PNK_POSHOLDER: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                MOZ_ASSERT(leaf.length() == 0);
+
+                return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
+            }
+
+            // Entirely undocumented nodes:
+            case PNK_MODULE: MOZ_FALLTHROUGH;
+            case PNK_DEBUGGER: MOZ_FALLTHROUGH;
+            case PNK_ELISION: MOZ_FALLTHROUGH;
+            case PNK_OBJECT_PROPERTY_NAME: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+                MOZ_ASSERT(leaf.length() == 0);
+
+                return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
+            }
+
+            // Stuff we don't handle yet:
+            case PNK_REGEXP: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+
+                return cx->new_<RegExpLiteral>(nullptr, TokenPos());
+            }
+        }
+
+        MOZ_CRASH("treeParse: out of switch()");
+        return nullptr;
     }
-}
-
+};
 
 
-MOZ_MUST_USE bool
-binSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& out, std::ostringstream& debug)
-{
-    if (!node) {
-        out.put(PNK_LIMIT);
-        debug << "PNK_LIMIT ";
-        return true;
+MOZ_MUST_USE bool binSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& out, std::ostringstream& debug) {
+    const UniquePtr<StringTree> tree(treeSerialize(cx, node, debug));
+    if (!tree) {
+        return false;
     }
-
-    // fprintf(stderr, "binSerialize %s\n", NAMES[node->getKind()]);
-
-    // Sanity check: add a header
-    out.write(HEADER, sizeof(HEADER));
-
-    ParseNodeKind kind = node->getKind();
-    out.write((const char*)&kind, sizeof(node->getKind()));
-    debug << NAMES[kind] << " ";
-    switch (node->getKind()) {
-        case PNK_LEXICALSCOPE:
-            // fprintf(stderr, "PNK_LEXICALSCOPE: placeholder implementation\n");
-            MOZ_FALLTHROUGH;
-        case PNK_FUNCTION:
-            if (!binSerialize(cx, node->pn_body, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_IF: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC: MOZ_FALLTHROUGH;
-        case PNK_EXPORT:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid2, out, debug)) {
-                return false;
-            }
-            return binSerialize(cx, node->pn_kid3, out, debug);
-        case PNK_CASE:
-            if (!binSerialize(cx, node->pn_left, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_right, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_WHILE: MOZ_FALLTHROUGH;
-        case PNK_DOWHILE: MOZ_FALLTHROUGH;
-        case PNK_FOR: MOZ_FALLTHROUGH;
-        case PNK_COMPREHENSIONFOR: MOZ_FALLTHROUGH;
-        case PNK_WITH: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_FROM: MOZ_FALLTHROUGH;
-        case PNK_ASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ADDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_SUBASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITXORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITANDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_LSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_RSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_URSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MULASSIGN: MOZ_FALLTHROUGH;
-        case PNK_DIVASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MODASSIGN: MOZ_FALLTHROUGH;
-        case PNK_POWASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ELEM: MOZ_FALLTHROUGH;
-        case PNK_COLON: MOZ_FALLTHROUGH;
-        case PNK_SHORTHAND: MOZ_FALLTHROUGH;
-        case PNK_SETTHIS: MOZ_FALLTHROUGH;
-        case PNK_SWITCH: MOZ_FALLTHROUGH;
-        case PNK_IMPORT: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC: MOZ_FALLTHROUGH;
-        case PNK_CLASSMETHOD: MOZ_FALLTHROUGH;
-        case PNK_CLASSNAMES: MOZ_FALLTHROUGH;
-        case PNK_NEWTARGET:
-            if (!binSerialize(cx, node->pn_left, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_right, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_FORIN: MOZ_FALLTHROUGH;
-        case PNK_FOROF:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            // No node->pn_kid2 for these nodes.
-            if (!binSerialize(cx, node->pn_kid3, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_FORHEAD:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid2, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid3, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_CONDITIONAL: MOZ_FALLTHROUGH;
-        case PNK_CLASS:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid2, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid3, out, debug)) {
-                return false;
-            }
-            return true;
-        // Unaries
-        case PNK_THROW: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_DEFAULT: MOZ_FALLTHROUGH;
-        case PNK_COMPUTED_NAME: MOZ_FALLTHROUGH;
-        case PNK_ARRAYPUSH: MOZ_FALLTHROUGH;
-        case PNK_SPREAD:
-            if (!binSerialize(cx, node->pn_kid, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_TRY:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid2, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid3, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_CLASSMETHODLIST: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_STATEMENTLIST: MOZ_FALLTHROUGH;
-        case PNK_CATCHLIST: MOZ_FALLTHROUGH;
-        case PNK_VAR: MOZ_FALLTHROUGH;
-        case PNK_LET: MOZ_FALLTHROUGH;
-        case PNK_CONST: MOZ_FALLTHROUGH;
-        case PNK_COMMA: MOZ_FALLTHROUGH;
-        case PNK_CALL: MOZ_FALLTHROUGH;
-        case PNK_GENEXP: MOZ_FALLTHROUGH;
-        case PNK_ARRAY: MOZ_FALLTHROUGH;
-        case PNK_OBJECT: MOZ_FALLTHROUGH;
-        case PNK_PARAMSBODY: MOZ_FALLTHROUGH;
-        case PNK_OR: MOZ_FALLTHROUGH;
-        case PNK_AND: MOZ_FALLTHROUGH;
-        case PNK_BITOR: MOZ_FALLTHROUGH;
-        case PNK_BITXOR: MOZ_FALLTHROUGH;
-        case PNK_BITAND: MOZ_FALLTHROUGH;
-        case PNK_EQ: MOZ_FALLTHROUGH;
-        case PNK_NE: MOZ_FALLTHROUGH;
-        case PNK_STRICTEQ: MOZ_FALLTHROUGH;
-        case PNK_STRICTNE: MOZ_FALLTHROUGH;
-        case PNK_LT: MOZ_FALLTHROUGH;
-        case PNK_LE: MOZ_FALLTHROUGH;
-        case PNK_GT: MOZ_FALLTHROUGH;
-        case PNK_GE: MOZ_FALLTHROUGH;
-        case PNK_LSH: MOZ_FALLTHROUGH;
-        case PNK_RSH: MOZ_FALLTHROUGH;
-        case PNK_URSH: MOZ_FALLTHROUGH;
-        case PNK_ADD: MOZ_FALLTHROUGH;
-        case PNK_SUB: MOZ_FALLTHROUGH;
-        case PNK_STAR: MOZ_FALLTHROUGH;
-        case PNK_DIV: MOZ_FALLTHROUGH;
-        case PNK_MOD: MOZ_FALLTHROUGH;
-        case PNK_POW: MOZ_FALLTHROUGH;
-        case PNK_NEW: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING_LIST: MOZ_FALLTHROUGH;
-        case PNK_TAGGED_TEMPLATE: MOZ_FALLTHROUGH;
-        case PNK_CALLSITEOBJ: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_SUPERCALL: MOZ_FALLTHROUGH;
-        case PNK_ARRAYCOMP: MOZ_FALLTHROUGH;
-        case PNK_INSTANCEOF: MOZ_FALLTHROUGH;
-        case PNK_IN: {
-            // fprintf(stderr, "binSerialize list of %d items\n", node->pn_count);
-            const size_t count = node->pn_count;
-            out.write((const char *)&count, sizeof(node->pn_count));
-            for (ParseNode* statement = node->pn_head; statement != nullptr; statement = statement->pn_next) {
-                if (!binSerialize(cx, statement, out, debug)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        case PNK_CATCH:
-            if (!binSerialize(cx, node->pn_kid1, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid2, out, debug)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_kid3, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_BREAK: MOZ_FALLTHROUGH;
-        case PNK_CONTINUE:
-            if (!binSerializeAtom(cx, node->pn_u.loopControl.label, out)) {
-                return false;
-            }
-            return true;
-        case PNK_RETURN: MOZ_FALLTHROUGH;
-        case PNK_SEMI:
-            if (!binSerialize(cx, node->pn_kid, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_LABEL:
-            if (!binSerializeAtom(cx, node->pn_atom, out)) {
-                return false;
-            }
-            if (!binSerialize(cx, node->pn_expr, out, debug)) {
-                return false;
-            }
-            return true;
-        case PNK_POS: MOZ_FALLTHROUGH;
-        case PNK_NEG: MOZ_FALLTHROUGH;
-        case PNK_VOID: MOZ_FALLTHROUGH;
-        case PNK_NOT: MOZ_FALLTHROUGH;
-        case PNK_BITNOT: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFNAME: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFEXPR: MOZ_FALLTHROUGH;
-        case PNK_PREINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_PREDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_DELETENAME: MOZ_FALLTHROUGH;
-        case PNK_DELETEPROP: MOZ_FALLTHROUGH;
-        case PNK_DELETEELEM: MOZ_FALLTHROUGH;
-        case PNK_DELETEEXPR: MOZ_FALLTHROUGH;
-        case PNK_MUTATEPROTO:
-            return binSerialize(cx, node->pn_kid, out, debug);
-        case PNK_DOT:
-            if (!binSerialize(cx, node->pn_expr, out, debug)) {
-                return false;
-            }
-            if (!binSerializeAtom(cx, node->pn_atom, out)) {
-                return false;
-            }
-            return true;
-        case PNK_NAME: MOZ_FALLTHROUGH;
-        case PNK_STRING: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING: {
-            if (!binSerializeAtom(cx, node->pn_atom, out)) { // FIXME: Double-check.
-                return false;
-            }
-            return true;
-        }
-        case PNK_NUMBER: {
-            double dval = node->pn_dval;
-            out.write((const char*)&dval, sizeof(dval));
-            DecimalPoint point = node->pn_u.number.decimalPoint;
-            out.write((const char*)&point, sizeof(point));
-            // fprintf(stderr, "binSerialize number: %f\n", dval);
-            return true;
-        }
-        // Nullaries
-        case PNK_TRUE: MOZ_FALLTHROUGH;
-        case PNK_FALSE: MOZ_FALLTHROUGH;
-        case PNK_NULL: MOZ_FALLTHROUGH;
-        case PNK_RAW_UNDEFINED: MOZ_FALLTHROUGH;
-        case PNK_GENERATOR: MOZ_FALLTHROUGH;
-        case PNK_NOP: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_BATCH_SPEC: MOZ_FALLTHROUGH;
-        case PNK_POSHOLDER:
-            return true;
-        case PNK_THIS: MOZ_FALLTHROUGH;
-        case PNK_YIELD: MOZ_FALLTHROUGH;
-        case PNK_YIELD_STAR: MOZ_FALLTHROUGH;
-        case PNK_AWAIT:
-            return binSerialize(cx, node->pn_kid, out, debug);
-        case PNK_SUPERBASE: MOZ_FALLTHROUGH;
-        case PNK_INITIALYIELD:
-            return binSerialize(cx, node->pn_kid, out, debug);
-
-        case PNK_LEXICALSCOPE:
-            // fprintf(stderr, "PNK_LEXICALSCOPE: placeholder implementation\n");
-            return binSerialize(cx, node->pn_body, out, debug);
-
-        // Stuff we don't handle yet:
-        case PNK_REGEXP:
-            MOZ_CRASH("TODO");
-            break;
-
-        // And now, the undocumented nodes:
-        case PNK_MODULE: MOZ_FALLTHROUGH;
-        case PNK_DEBUGGER: MOZ_FALLTHROUGH;
-        case PNK_ELISION: MOZ_FALLTHROUGH;
-        case PNK_OBJECT_PROPERTY_NAME:
-            return true;
-        case PNK_LIMIT:
-            MOZ_CRASH("Invalid token PNK_LIMIT");
-    }
-    MOZ_CRASH("Should not have reached this point");
-}
-
-MOZ_MUST_USE
-JSAtom* binParseAtom(JSContext* cx, std::istringstream& in) {
-    OptionValue op;
-
-    in.read((char*)&op, sizeof(op));
-    if (op == OptionIsHere) {
-        size_t size;
-        in.read((char*)&size, sizeof(size));
-
-        char* buf(new char[size + 1]);
-        in.read(buf, size);
-
-        {
-            buf[size] = '\0';
-            // fprintf(stderr, "binParseAtom: %s (%zu)\n", buf, size);
-        }
-
-        // FIXME: We don't need copy.
-        JSAtom* result = js::Atomize(cx, buf, size);
-        delete[] buf;
-        return result;
-    } else if (op == OptionIsAbsent) {
-        return nullptr;
-    }
-    MOZ_CRASH();
-}
-
-MOZ_MUST_USE
-bool binParsePropertyName(JSContext* cx, std::istringstream& in, MutableHandle<PropertyName*> label) {
-    JSAtom* atom = binParseAtom(cx, in);
-    label.set(atom->asPropertyName());
+    tree->write(out);
     return true;
 }
-
-MOZ_MUST_USE
-ParseNode* binParseAux(JSContext* cx, ParseNodeKind kind, std::istringstream& in, std::ostringstream& debug);
-MOZ_MUST_USE
-ParseNode* binParse(JSContext* cx, std::istringstream& in, std::ostringstream& debug) {
-    static unsigned int depth = 0;
-    char header[sizeof(HEADER)];
-    in.read(header, sizeof(header));
-
-    {
-        char tmp = header[sizeof(HEADER) - 1];
-        header[sizeof(HEADER) - 1] = 0;
-        // fprintf(stderr, "Expected header %s\n", HEADER);
-        // fprintf(stderr, "Got header %s\n", header);
-        header[sizeof(HEADER) - 1] = tmp;
-    }
-
-    if (strncmp(header, HEADER, sizeof(HEADER)) != 0) {
-        for (size_t i = 0; i < sizeof(HEADER); ++i) {
-            // fprintf(stderr, "%u ", header[i]);
-        }
-        // fprintf(stderr, "\n");
-        MOZ_CRASH("Bad header");
-    }
-
-    ParseNodeKind kind;
-    in.read((char*)&kind, sizeof(ParseNodeKind));
-
-    MOZ_ASSERT(kind < ArrayLength(NAMES));
-    // fprintf(stderr, ">> [%u] binParse %s\n", depth, NAMES[kind]);
-    ++depth;
-    debug.write(NAMES[kind], strlen(NAMES[kind]));
-    debug.write(" ", 1);
-
-    ParseNode* result = binParseAux(cx, kind, in, debug);
-
-    --depth;
-    // fprintf(stderr, "<< [%u] binParse %s\n", depth, NAMES[kind]);
-
-#if 0
-    // Sanity check, to find out as early as possible if we failed to swallow all our bytes.
-    if (!in.eof()) {
-        const size_t pos = in.tellg();
-        in.read(header, sizeof(header));
-
-        if (strncmp(header, HEADER, sizeof(HEADER)) != 0) {
-            char tmp = header[sizeof(HEADER) - 1];
-            header[sizeof(HEADER) - 1] = 0;
-            // fprintf(stderr, "ERROR: Misaligned read.\n");
-            // fprintf(stderr, "Expected header %s\n", HEADER);
-            // fprintf(stderr, "Got header %s\n", header);
-            header[sizeof(HEADER) - 1] = tmp;
-            MOZ_CRASH();
-        }
-
-        in.seekg(pos);
-    }
-#endif
-    return result;
+MOZ_MUST_USE ParseNode* binParse(JSContext* cx, std::istringstream& in, std::ostringstream& debug) {
+    TreeParser parser(cx, in, debug);
+    return parser.parseNode();
 }
 
-MOZ_MUST_USE
-ParseNode* binParseAux(JSContext* cx, ParseNodeKind kind, std::istringstream& in, std::ostringstream& debug) {
-    switch (kind) {
-        case PNK_LIMIT:
-            return nullptr;
-        case PNK_FUNCTION: {
-            UniquePtr<ParseNode> body(binParse(cx, in, debug));
-            CodeNode* result(cx->new_<CodeNode>(kind, JSOP_NOP, TokenPos()));
-            result->pn_body = body.release();
-            return result;
-        }
-        case PNK_IF: {
-            UniquePtr<ParseNode> cond(binParse(cx, in, debug));
-            MOZ_ASSERT(cond != nullptr);
-            UniquePtr<ParseNode> thenBranch(binParse(cx, in, debug));
-            MOZ_ASSERT(thenBranch != nullptr);
-            UniquePtr<ParseNode> elseBranch(binParse(cx, in, debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, cond.release(), thenBranch.release(), elseBranch.release());
-        }
-        case PNK_CASE: {
-            UniquePtr<ParseNode> expr(binParse(cx, in, debug));
-            UniquePtr<ParseNode> statement(binParse(cx, in, debug));
-            MOZ_ASSERT(statement != nullptr);
-            return cx->new_<CaseClause>(expr.release(), statement.release(), 0);
-        }
-        case PNK_WHILE: MOZ_FALLTHROUGH;
-        case PNK_DOWHILE: MOZ_FALLTHROUGH;
-        case PNK_FOR: MOZ_FALLTHROUGH;
-        case PNK_COMPREHENSIONFOR: MOZ_FALLTHROUGH;
-        case PNK_WITH: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_FROM: MOZ_FALLTHROUGH;
-        case PNK_ASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ADDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_SUBASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITXORASSIGN: MOZ_FALLTHROUGH;
-        case PNK_BITANDASSIGN: MOZ_FALLTHROUGH;
-        case PNK_LSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_RSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_URSHASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MULASSIGN: MOZ_FALLTHROUGH;
-        case PNK_DIVASSIGN: MOZ_FALLTHROUGH;
-        case PNK_MODASSIGN: MOZ_FALLTHROUGH;
-        case PNK_POWASSIGN: MOZ_FALLTHROUGH;
-        case PNK_ELEM: MOZ_FALLTHROUGH;
-        case PNK_COLON: MOZ_FALLTHROUGH;
-        case PNK_SHORTHAND: MOZ_FALLTHROUGH;
-        case PNK_SETTHIS: MOZ_FALLTHROUGH;
-        case PNK_SWITCH: MOZ_FALLTHROUGH;
-        case PNK_IMPORT: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC: MOZ_FALLTHROUGH;
-        case PNK_CLASSMETHOD: MOZ_FALLTHROUGH;
-        case PNK_CLASSNAMES: MOZ_FALLTHROUGH;
-        case PNK_NEWTARGET: {
-            UniquePtr<ParseNode> left(binParse(cx, in, debug));
-            MOZ_ASSERT(left != nullptr);
-            UniquePtr<ParseNode> right(binParse(cx, in, debug));
-            MOZ_ASSERT(right != nullptr);
-            return cx->new_<BinaryNode>(kind, JSOP_NOP, TokenPos(), left.release(), right.release());
-        }
-        case PNK_FORIN: MOZ_FALLTHROUGH;
-        case PNK_FOROF: {
-             JSOp op = kind == PNK_FORIN ? JSOP_ITER : JSOP_NOP;
-             UniquePtr<ParseNode> kid1(binParse(cx, in, debug));
-             UniquePtr<ParseNode> kid3(binParse(cx, in, debug));
-             return cx->new_<TernaryNode>(kind, op, kid1.release(), nullptr, kid3.release());
-        }
-        case PNK_TRY: MOZ_FALLTHROUGH;
-        case PNK_FORHEAD: MOZ_FALLTHROUGH;
-        case PNK_CONDITIONAL: {
-            UniquePtr<ParseNode> kid1(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid2(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid3(binParse(cx, in, debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, kid1.release(), kid2.release(), kid3.release());
-        }
-        case PNK_THROW: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_DEFAULT: MOZ_FALLTHROUGH;
-        case PNK_COMPUTED_NAME: MOZ_FALLTHROUGH;
-        case PNK_ARRAYPUSH: MOZ_FALLTHROUGH;
-        case PNK_SPREAD: {
-            UniquePtr<ParseNode> kid(binParse(cx, in, debug));
-            JSOp op = kind == PNK_THROW ? JSOP_THROW :
-                      kind == PNK_ARRAYPUSH ? JSOP_ARRAYPUSH :
-                      JSOP_NOP;
-            return cx->new_<UnaryNode>(kind, op, TokenPos(), kid.release());
-        }
-        case PNK_STATEMENTLIST: MOZ_FALLTHROUGH;
-        case PNK_CATCHLIST: MOZ_FALLTHROUGH;
-        case PNK_VAR: MOZ_FALLTHROUGH;
-        case PNK_LET: MOZ_FALLTHROUGH;
-        case PNK_CONST: MOZ_FALLTHROUGH;
-        case PNK_COMMA: MOZ_FALLTHROUGH;
-        case PNK_CALL: MOZ_FALLTHROUGH;
-        case PNK_GENEXP: MOZ_FALLTHROUGH;
-        case PNK_ARRAY: MOZ_FALLTHROUGH;
-        case PNK_OBJECT: MOZ_FALLTHROUGH;
-        case PNK_PARAMSBODY: MOZ_FALLTHROUGH;
-        case PNK_OR: MOZ_FALLTHROUGH;
-        case PNK_AND: MOZ_FALLTHROUGH;
-        case PNK_BITOR: MOZ_FALLTHROUGH;
-        case PNK_BITXOR: MOZ_FALLTHROUGH;
-        case PNK_BITAND: MOZ_FALLTHROUGH;
-        case PNK_EQ: MOZ_FALLTHROUGH;
-        case PNK_NE: MOZ_FALLTHROUGH;
-        case PNK_STRICTEQ: MOZ_FALLTHROUGH;
-        case PNK_STRICTNE: MOZ_FALLTHROUGH;
-        case PNK_LT: MOZ_FALLTHROUGH;
-        case PNK_LE: MOZ_FALLTHROUGH;
-        case PNK_GT: MOZ_FALLTHROUGH;
-        case PNK_GE: MOZ_FALLTHROUGH;
-        case PNK_LSH: MOZ_FALLTHROUGH;
-        case PNK_RSH: MOZ_FALLTHROUGH;
-        case PNK_URSH: MOZ_FALLTHROUGH;
-        case PNK_ADD: MOZ_FALLTHROUGH;
-        case PNK_SUB: MOZ_FALLTHROUGH;
-        case PNK_STAR: MOZ_FALLTHROUGH;
-        case PNK_DIV: MOZ_FALLTHROUGH;
-        case PNK_MOD: MOZ_FALLTHROUGH;
-        case PNK_POW: MOZ_FALLTHROUGH;
-        case PNK_NEW: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING_LIST: MOZ_FALLTHROUGH;
-        case PNK_TAGGED_TEMPLATE: MOZ_FALLTHROUGH;
-        case PNK_CALLSITEOBJ: MOZ_FALLTHROUGH;
-        case PNK_ARRAYCOMP: MOZ_FALLTHROUGH;
-        case PNK_IN: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_SPEC_LIST: MOZ_FALLTHROUGH;
-        case PNK_CLASSMETHODLIST: MOZ_FALLTHROUGH;
-        case PNK_SUPERCALL: MOZ_FALLTHROUGH;
-        case PNK_INSTANCEOF: {
-            uint32_t count;
-            ParseNode* latest = nullptr;
-            in.read((char*)&count, sizeof(count));
-            // fprintf(stderr, "binParse %s list of %d items\n", NAMES[kind], count);
-
-            UniquePtr<ListNode> result(cx->new_<ListNode>(kind, TokenPos()));
-
-            for (uint32_t i = 0; i < count; ++i) {
-                ParseNode* child = binParse(cx, in, debug);
-                if (i == 0) {
-                    result->pn_head = child;
-                    result->pn_tail = &result->pn_head;
-                } else {
-                    latest->pn_next = child;
-                    result->pn_tail  = &latest->pn_next;
-                }
-                latest = child;
-                result->pn_count++; // FIXME: Incrementing progressively in case it helps if the destructor is called early.
-            }
-
-            // fprintf(stderr, "binParse %s list of %d items complete\n", NAMES[kind], count);
-            return result.release();
-        }
-        case PNK_CATCH: {
-            UniquePtr<ParseNode> kid1(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid2(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid3(binParse(cx, in, debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, kid1.release(), kid2.release(), kid3.release());
-        }
-        case PNK_BREAK: {
-            RootedPropertyName label(cx);
-            if (!binParsePropertyName(cx, in, &label)) {
-                label = nullptr;
-            }
-            return cx->new_<BreakStatement>(label, TokenPos());
-        }
-        case PNK_CONTINUE: {
-            RootedPropertyName label(cx);
-            if (!binParsePropertyName(cx, in, &label)) {
-                label = nullptr;
-            }
-            return cx->new_<ContinueStatement>(label, TokenPos());
-        }
-        case PNK_LABEL: {
-            RootedPropertyName label(cx);
-            if (!binParsePropertyName(cx, in, &label)) {
-                label = nullptr;
-            }
-            UniquePtr<ParseNode> expr(binParse(cx, in, debug));
-            return cx->new_<LabeledStatement>(label, expr.release(), 0);
-        }
-        // JSOP_NOP
-        case PNK_SEMI: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFNAME: MOZ_FALLTHROUGH;
-        case PNK_TYPEOFEXPR: MOZ_FALLTHROUGH;
-        case PNK_PREINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTINCREMENT: MOZ_FALLTHROUGH;
-        case PNK_PREDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_POSTDECREMENT: MOZ_FALLTHROUGH;
-        case PNK_DELETENAME: MOZ_FALLTHROUGH;
-        case PNK_DELETEPROP: MOZ_FALLTHROUGH;
-        case PNK_DELETEELEM: MOZ_FALLTHROUGH;
-        case PNK_DELETEEXPR: MOZ_FALLTHROUGH;
-        // Operation
-        case PNK_RETURN: MOZ_FALLTHROUGH;
-        case PNK_POS: MOZ_FALLTHROUGH;
-        case PNK_NEG: MOZ_FALLTHROUGH;
-        case PNK_VOID: MOZ_FALLTHROUGH;
-        case PNK_NOT: MOZ_FALLTHROUGH;
-        case PNK_BITNOT: {
-            JSOp op =
-                kind == PNK_RETURN ? JSOP_RETURN :
-                kind == PNK_POS ? JSOP_POS :
-                kind == PNK_NEG ? JSOP_NEG :
-                kind == PNK_VOID ? JSOP_VOID :
-                kind == PNK_NOT ? JSOP_NOT :
-                kind == PNK_BITNOT ? JSOP_BITNOT :
-                JSOP_NOP;
-            UniquePtr<ParseNode> kid(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, op, TokenPos(), kid.release());
-        }
-        case PNK_DOT: {
-            UniquePtr<ParseNode> expr(binParse(cx, in, debug));
-            RootedPropertyName name(cx);
-            if (!binParsePropertyName(cx, in, &name)) {
-                return nullptr; // FIXME: Assert?
-            }
-            return cx->new_<PropertyAccess>(expr.release(), name, 0, 0);
-        }
-        case PNK_STRING: MOZ_FALLTHROUGH;
-        case PNK_TEMPLATE_STRING: {
-            UniquePtr<JSAtom> atom(binParseAtom(cx, in));
-             // FIXME: No clue why it seems to need one more byte.
-            char buf[1];
-            in.read((char*)buf, sizeof(buf));
-            return cx->new_<NullaryNode>(kind, JSOP_NOP, TokenPos(), atom.release());
-        }
-        case PNK_NAME: {
-            UniquePtr<JSAtom> atom(binParseAtom(cx, in));
-            return cx->new_<NameNode>(PNK_NAME, JSOP_GETNAME, atom.release(), TokenPos());
-        }
-        case PNK_NUMBER: {
-            double dval;
-            in.read((char*)&dval, sizeof(dval));
-            DecimalPoint point;
-            in.read((char*)&point, sizeof(point));
-            UniquePtr<NullaryNode> result(cx->new_<NullaryNode>(PNK_NUMBER, TokenPos()));
-            result->initNumber(dval, point);
-            // fprintf(stderr, "binParse number: %f\n", dval);
-            return result.release();
-        }
-        case PNK_TRUE: MOZ_FALLTHROUGH;
-        case PNK_FALSE: {
-            return cx->new_<BooleanLiteral>(kind == PNK_TRUE, TokenPos());
-        }
-        case PNK_NULL: {
-            return cx->new_<NullLiteral>(TokenPos());
-        }
-        case PNK_RAW_UNDEFINED: {
-            return cx->new_<RawUndefinedLiteral>(TokenPos());
-        }
-        case PNK_GENERATOR: MOZ_FALLTHROUGH;
-        case PNK_NOP: MOZ_FALLTHROUGH;
-        case PNK_ELISION: MOZ_FALLTHROUGH;
-        case PNK_OBJECT_PROPERTY_NAME: MOZ_FALLTHROUGH;
-        case PNK_MODULE: MOZ_FALLTHROUGH;
-        case PNK_DEBUGGER: MOZ_FALLTHROUGH;
-        case PNK_EXPORT_BATCH_SPEC: MOZ_FALLTHROUGH;
-        case PNK_POSHOLDER: {
-            return cx->new_<NullaryNode>(kind, TokenPos());
-        }
-        case PNK_THIS: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<ThisLiteral>(TokenPos(), value.release());
-        }
-        case PNK_YIELD: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_YIELD, TokenPos(), value.release());
-        }
-        case PNK_YIELD_STAR: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), value.release());
-        }
-        case PNK_AWAIT: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_AWAIT, TokenPos(), value.release());
-        }
-        case PNK_SUPERBASE: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), value.release());
-        }
-        case PNK_INITIALYIELD: {
-            UniquePtr<ParseNode> value(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_INITIALYIELD, TokenPos(), value.release());
-        }
-        case PNK_REGEXP: {
-            MOZ_CRASH("No idea how to handle this yet");
-            return nullptr;
-        }
-        case PNK_LEXICALSCOPE: {
-            // fprintf(stderr, "PNK_LEXICALSCOPE: placeholder parser\n");
-            UniquePtr<ParseNode> body(binParse(cx, in, debug));
-            return cx->new_<LexicalScopeNode>(nullptr, body.release());
-        }
-        case PNK_MUTATEPROTO: {
-            // fprintf(stderr, "PNK_MUTATEPROTO: placeholder parser\n");
-            UniquePtr<ParseNode> kid(binParse(cx, in, debug));
-            return cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), kid.release());
-        }
-        case PNK_EXPORT: MOZ_FALLTHROUGH;
-        case PNK_IMPORT_SPEC: {
-            UniquePtr<ParseNode> kid1(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid2(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid3(binParse(cx, in, debug));
-            return cx->new_<TernaryNode>(kind, JSOP_NOP, kid1.release(), kid2.release(), kid3.release());
-        }
-        case PNK_CLASS: {
-            UniquePtr<ParseNode> kid1(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid2(binParse(cx, in, debug));
-            UniquePtr<ParseNode> kid3(binParse(cx, in, debug));
-            return cx->new_<ClassNode>(kid1.release(), kid2.release(), kid3.release());
-        }
-
-    }
-    MOZ_CRASH("Should not have reached that point");
-}
-
-#endif // 0
-
 } // namespace frontend
 } // namespace js
--- a/js/src/frontend/BinaryAST.h
+++ b/js/src/frontend/BinaryAST.h
@@ -61,17 +61,19 @@ struct StringTree {
     const mozilla::Vector<UniquePtr<StringTree>>& children() const;
 
     static StringTree* read(JSContext* cx, std::istringstream& in);
     void write(std::ostringstream& out) const;
 
     ~StringTree();
 };
 
+#if 0
 MOZ_MUST_USE StringTree*
 treeSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& debug);
 
 MOZ_MUST_USE ParseNode*
 treeParse(JSContext* cx, const StringTree* tree, std::ostringstream& debug);
+#endif // 0
 }
 }
 
 #endif // frontend_BinaryAST_h
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2203,89 +2203,84 @@ Parser<FullParseHandler>::globalBody(Glo
     if (!bindings)
         return nullptr;
     globalsc->bindings = *bindings;
 
     fprintf(stderr, "Parser::globalBody from source %d times: average %f ms\n", (int)REPEAT, totalFromSource.ToMilliseconds() / (int)REPEAT);
 
     fprintf(stderr, "Parser::globalBody serializing\n");
 
-    // FIXME: For the time being, we check with tree (de)serialization.
-    // FIXME: For a real usecase, we should check with binary (de)serialization.
+    std::ostringstream serialize;
     std::ostringstream debug;
-    UniquePtr<StringTree> tree(treeSerialize(context, body, debug));
-    if (!tree) {
+    if (!binSerialize(context, body, serialize, debug)) {
         MOZ_CRASH("Serialization failed");
     }
 
-    std::ostringstream out_tree;
-    tree->write(out_tree);
-    const auto buf_tree = out_tree.str();
 
     std::cerr << "Parser::globalBody serialization success\n";
-    const std::string witness = debug.str();
+    const auto buf_serialize = serialize.str();
+    const auto witness = debug.str();
 
     mozilla::TimeDuration totalFromBin;
 
     for (size_t i = 0; i < REPEAT * 10; ++i) {
         std::cerr << "Parser::globalBody binary parsing" << i << "\n";
-        std::ostringstream trace;
-        std::istringstream in_tree(buf_tree);
+        std::ostringstream debug_parse;
+        std::istringstream in_tree(buf_serialize);
 
         mozilla::TimeStamp start = mozilla::TimeStamp::Now();
 
-        UniquePtr<StringTree> parsed_tree(StringTree::read(context, in_tree));
-        UniquePtr<ParseNode> body2(treeParse(context, parsed_tree.get(), trace));
+        UniquePtr<ParseNode> body2(binParse(context, in_tree, debug_parse));
 
         mozilla::TimeStamp stop = mozilla::TimeStamp::Now();
         mozilla::TimeDuration delta = stop - start;
         totalFromBin += delta;
 
         // Sanity checks.
         MOZ_ASSERT(body2);
-        if (trace.str().compare(witness) != 0) {
-            auto debug = trace.str();
+        if (debug_parse.str().compare(witness) != 0) {
+            auto witness_parse = debug_parse.str();
             size_t i;
-            for (i = 0; i < debug.length() && i < witness.length(); ++i) {
-                if (debug[i] != witness[i]) {
+            for (i = 0; i < witness_parse.length() && i < witness.length(); ++i) {
+                if (witness_parse[i] != witness[i]) {
                     break;
                 }
             }
-            if (i < debug.length() || i < witness.length()) {
+            if (i < witness_parse.length() || i < witness.length()) {
                 std::cerr << "Reserialization doesn't produce the same witness\n"
-                    << "Common prefix: (" << i << " chars)\n '" << std::string(&debug[0], i) << "'"
-                    << "\n\n Witness: '" << std::string(&witness[i], witness.length() - i) << "'\n"
-                    << "\n\n Debug: '" << std::string(&debug[i], debug.length() - i) << "'\n"
-                    << "\n\n Length difference: " << (witness.length() - debug.length()) << "\n";
+                    << "Common prefix: (" << i << " chars)\n '" << std::string(&witness_parse[0], i) << "'"
+                    << "\n\n Bin serialization: '" << std::string(&witness[i], witness.length() - i) << "'\n"
+                    << "\n\n Bin parse: '" << std::string(&witness_parse[i], witness_parse.length() - i) << "'\n"
+                    << "\n\n Length difference: " << (witness.length() - witness_parse.length()) << "\n";
                 MOZ_CRASH("Parser::globalBody serialization doesn't produce the same witness.");
             }
         } else {
             fprintf(stderr, "Parser::globalBody binary parsing seems to work.\n");
         }
 
-        std::ostringstream sanity_debug;
-        UniquePtr<StringTree> tree2(treeSerialize(context, body2.get(), sanity_debug));
-        if (!tree2) {
+        std::ostringstream reserialize;
+        std::ostringstream debug_reserialize;
+        if (!binSerialize(context, body2.get(), reserialize, debug_reserialize)) {
             MOZ_CRASH("Re-serialization failed");
         }
 
-        const auto debug = sanity_debug.str();
-        if (debug.compare(witness) != 0) {
+        const auto witness_reserialize = debug_reserialize.str();
+        if (witness_reserialize.compare(witness) != 0) {
             size_t i;
-            for (i = 0; i < debug.length() && i < witness.length(); ++i) {
-                if (debug[i] != witness[i]) {
+            for (i = 0; i < witness_reserialize.length() && i < witness.length(); ++i) {
+                if (witness_reserialize[i] != witness[i]) {
                     break;
                 }
             }
-            if (i < debug.length() || i < witness.length()) {
+            if (i < witness_reserialize.length() || i < witness.length()) {
                 std::cerr << "Reserialization doesn't match\n"
-                    << "Common prefix: (" << i << " chars)\n '" << std::string(&debug[0], i) << "'"
+                    << "Common prefix: (" << i << " chars)\n '" << std::string(&witness_reserialize[0], i) << "'"
                     << "\n\n Witness: '" << std::string(&witness[i], witness.length() - i) << "'\n"
-                    << "\n\n Debug: '" << std::string(&debug[i], debug.length() - i) << "'\n"
-                    << "\n\n Length difference: " << (witness.length() - debug.length()) << "\n";
+                    << "\n\n Debug: '" << std::string(&witness_reserialize[i], witness_reserialize.length() - i) << "'\n"
+                    << "\n\n Length difference: " << (witness.length() - witness_reserialize.length()) << "\n";
                 MOZ_CRASH("Parser::globalBody re-serialization doesn't produce the same witness.");
             }
         }
 
         // FIXME: Compare trees.
     }
 
     fprintf(stderr, "Parser::globalBody from bin %d times: average %f ms\n", (int)(REPEAT * 10), totalFromBin.ToMilliseconds() / (int)(REPEAT * 10));