Bug 1349917 - Early experiments with a binary format draft
authorDavid Teller <dteller@mozilla.com>
Wed, 12 Apr 2017 20:32:39 +0200
changeset 561484 b8e8671817db436258a4354b21af56f72c3c9298
parent 559608 731639fccc709a4dd95fed7e9dda88efb2227906
child 562839 28c68f2e0b552cd5c522cf76642df86de7a3b94e
push id53742
push userdteller@mozilla.com
push dateWed, 12 Apr 2017 18:36:51 +0000
bugs1349917
milestone55.0a1
Bug 1349917 - Early experiments with a binary format MozReview-Commit-ID: Btz8ZLPhVqg
js/src/frontend/BinaryAST.cpp
js/src/frontend/BinaryAST.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinaryAST.cpp
@@ -0,0 +1,1639 @@
+// ---- Ongoing experiment: binary ast serializer/parser
+
+#include <iostream>
+#include <istream>
+#include <sstream>
+
+#include "mozilla/Attributes.h"
+#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);
+*/
+
+enum OptionValue {
+    OptionIsHere = -1,
+    OptionIsAbsent = -2,
+};
+
+// A (pretty long) header to simplify debugging.
+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();
+    for (const UniquePtr<StringTree>& item: concat->children) {
+        total += item->byteLength
+         + sizeof HEADER
+         + sizeof FOOTER
+         + sizeof item->variantKind
+         + sizeof item->kind
+         + sizeof item->byteLength;
+    }
+    return total;
+}
+
+size_t totalByteLength(const StringTree::Leaf* concat) {
+    return concat->data.length();
+}
+/*
+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*>());
+    }
+}
+*/
+
+StringTree::~StringTree()
+{
+   switch (variantKind) {
+       case VariantKind::IsLeaf:
+           delete data.leaf;
+           return;
+       case VariantKind::IsConcat:
+           delete data.concat;
+           return;
+   }
+   MOZ_CRASH();
+}
+
+StringTree::StringTree(ParseNodeKind k, Leaf* leaf)
+    : variantKind(VariantKind::IsLeaf)
+    , data(leaf)
+    , byteLength(totalByteLength(leaf))
+    , kind(k)
+{}
+
+StringTree::Leaf::Leaf()
+    : data("")
+{ }
+
+StringTree::Leaf::Leaf(const std::string&& data_)
+    : data(data_)
+{ }
+
+/*static*/ StringTree*
+StringTree::makeLeaf(JSContext* cx, ParseNodeKind kind)
+{
+    return cx->new_<StringTree>(kind, cx->new_<StringTree::Leaf>());
+}
+
+/*static*/ StringTree*
+StringTree::makeLeaf(JSContext* cx, ParseNodeKind kind, std::string&& data)
+{
+    UniquePtr<Leaf> leaf(cx->new_<StringTree::Leaf>(Move(data)));
+    return cx->new_<StringTree>(kind, leaf.release());
+}
+
+bool
+StringTree::isLeaf() const {
+    return variantKind == StringTree::VariantKind::IsLeaf;
+}
+
+const StringTree::Leaf&
+StringTree::asLeaf() const {
+    MOZ_ASSERT(isLeaf());
+    return *data.leaf;
+}
+
+StringTree::StringTree(ParseNodeKind k, Concat* concat)
+    : variantKind(VariantKind::IsConcat)
+    , data(concat)
+    , byteLength(totalByteLength(concat))
+    , kind(k)
+{}
+
+StringTree::Concat::Concat(mozilla::Vector<UniquePtr<StringTree>>&& children_)
+    : children(Move(children_))
+{}
+
+/*static*/ StringTree*
+StringTree::makeConcat(JSContext* cx, ParseNodeKind kind, UniquePtr<StringTree>&& child)
+{
+    mozilla::Vector<UniquePtr<StringTree>> children;
+    if (!children.append(Move(child))) {
+        MOZ_CRASH();
+    }
+
+    return makeConcat(cx, kind, Move(children));
+}
+
+/*static*/ StringTree*
+StringTree::makeConcat(JSContext* cx, ParseNodeKind kind, mozilla::Vector<UniquePtr<StringTree>>&& children)
+{
+    UniquePtr<Concat> concat(cx->new_<StringTree::Concat>(Move(children)));
+    return cx->new_<StringTree>(kind, concat.release());
+}
+
+const StringTree::Concat&
+StringTree::asConcat() const {
+    MOZ_ASSERT(variantKind == StringTree::VariantKind::IsConcat);
+    return *data.concat;
+}
+
+const mozilla::Vector<UniquePtr<StringTree>>&
+StringTree::children() const {
+    return asConcat().children;
+}
+
+/*static*/ StringTree*
+StringTree::read(JSContext* cx, std::istringstream& in) {
+    char header[sizeof HEADER + 1];
+    in.read(header, sizeof HEADER);
+    if (strncmp(header, HEADER, sizeof HEADER) != 0) {
+        MOZ_CRASH("Bad header");
+    }
+
+    ParseNodeKind kind;
+    in.read((char*)&kind, sizeof kind);
+    if (kind >= mozilla::ArrayLength(NAMES)) {
+        MOZ_CRASH("Bad kind");
+    }
+
+    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);
+
+    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 {
+        size_t length = 0;
+        in.read((char*)&length, sizeof length);
+
+        mozilla::Vector<UniquePtr<StringTree>> children;
+        // FIXME: Prealloc.
+        for (size_t i = 0; i < length; ++i) {
+            if (!children.append(UniquePtr<StringTree>(StringTree::read(cx, in)))) {
+                MOZ_CRASH();
+                return nullptr;
+            }
+        }
+        result = UniquePtr<StringTree>(StringTree::makeConcat(cx, kind, Move(children)));
+    }
+    const size_t byteStop = in.tellg();
+
+    MOZ_ASSERT(byteStop - byteStart == 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*)&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;
+    } 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);
+        }
+    }
+    const size_t byteStop = out.tellp();
+    MOZ_ASSERT(byteLength == byteStop - byteStart);
+
+    out.write(FOOTER, sizeof(FOOTER));
+}
+
+
+using Leaf = StringTree::Leaf;
+using Concat = StringTree::Concat;
+
+void serializeAtom(JSContext* cx, JSAtom* atom, std::string& result)
+{
+    std::ostringstream out;
+    if (atom) {
+        OptionValue kind(OptionIsHere);
+        out.write((const char*)&kind, sizeof kind);
+
+        RootedString string(cx, atom);
+        JSAutoByteString bs;
+        if (!bs.encodeUtf8(cx, string)) {
+            MOZ_CRASH();
+        }
+
+        const size_t size = strlen(bs.ptr());
+        out.write((const char*)&size, sizeof size);
+        out.write(bs.ptr(), size);
+    } else {
+        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);
+
+    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 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]);
+    debug << NAMES[kind] << " ";
+
+    switch (kind) {
+        // Empty tree
+        case PNK_LIMIT: {
+            return StringTree::makeLeaf(cx, PNK_LIMIT);
+        }
+        // Body
+        case PNK_LEXICALSCOPE:
+            // fprintf(stderr, "PNK_LEXICALSCOPE: placeholder implementation\n");
+            MOZ_FALLTHROUGH;
+        case PNK_FUNCTION: {
+            UniquePtr<StringTree> body(treeSerialize(cx, node->pn_body, debug));
+            if (!body) {
+                return nullptr;
+            }
+            return StringTree::makeConcat(cx, kind, Move(body));
+        }
+        // kid1, kid2, kid3
+        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: {
+            mozilla::Vector<UniquePtr<StringTree>> children;
+            if (!children.append(UniquePtr<StringTree>(treeSerialize(cx, node->pn_kid1, debug)))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(UniquePtr<StringTree>(treeSerialize(cx, node->pn_kid2, debug)))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(UniquePtr<StringTree>(treeSerialize(cx, node->pn_kid3, debug)))) {
+                MOZ_CRASH();
+            }
+            return StringTree::makeConcat(cx, kind, Move(children));
+        }
+
+        // left, right
+        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: {
+            mozilla::Vector<UniquePtr<StringTree>> children;
+            if (!children.append(Move(UniquePtr<StringTree>(treeSerialize(cx, node->pn_left, debug))))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(Move(UniquePtr<StringTree>(treeSerialize(cx, node->pn_right, debug))))) {
+                MOZ_CRASH();
+            }
+            return StringTree::makeConcat(cx, kind, Move(children));
+        }
+
+        // kid1, kid3
+        case PNK_FORIN: MOZ_FALLTHROUGH;
+        case PNK_FOROF: {
+            mozilla::Vector<UniquePtr<StringTree>> children;
+            // No node->pn_kid2 for these nodes.
+            if (!children.append(UniquePtr<StringTree>(treeSerialize(cx, node->pn_kid1, debug)))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(UniquePtr<StringTree>(treeSerialize(cx, node->pn_kid3, debug)))) {
+                MOZ_CRASH();
+            }
+            return StringTree::makeConcat(cx, kind, Move(children));
+        }
+
+        // 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: {
+            UniquePtr<StringTree> kid(treeSerialize(cx, node->pn_kid, debug));
+            return StringTree::makeConcat(cx, kind, Move(kid));
+        }
+
+        // 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: {
+            mozilla::Vector<UniquePtr<StringTree>> children;
+
+            for (ParseNode* statement = node->pn_head; statement != nullptr; statement = statement->pn_next) {
+                if (!children.append(treeSerialize(cx, statement, debug))) {
+                    MOZ_CRASH();
+                }
+            }
+            return StringTree::makeConcat(cx, kind, Move(children));
+        }
+
+        // Labels
+        case PNK_BREAK: MOZ_FALLTHROUGH;
+        case PNK_CONTINUE: {
+            std::string label;
+            serializeAtom(cx, node->pn_u.loopControl.label, label);
+            return StringTree::makeLeaf(cx, kind, Move(label));
+        }
+        case PNK_LABEL: {
+            std::string label;
+            serializeAtom(cx, node->pn_u.loopControl.label, label);
+
+            mozilla::Vector<UniquePtr<StringTree>> children;
+            if (!children.append(StringTree::makeLeaf(cx, kind, Move(label)))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(treeSerialize(cx, node->pn_expr, debug))) {
+                MOZ_CRASH();
+            }
+            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)))) {
+                MOZ_CRASH();
+            }
+            if (!children.append(treeSerialize(cx, node->pn_expr, debug))) {
+                MOZ_CRASH();
+            }
+            return StringTree::makeConcat(cx, kind, Move(children));
+        }
+
+        // Strings
+        case PNK_NAME: MOZ_FALLTHROUGH;
+        case PNK_STRING: MOZ_FALLTHROUGH;
+        case PNK_TEMPLATE_STRING: {
+            std::string data;
+            serializeAtom(cx, node->pn_atom, data);
+            return StringTree::makeLeaf(cx, kind, Move(data));
+        }
+
+        case PNK_NUMBER: {
+            std::string data;
+            double dval = node->pn_dval;
+            data.append((const char*)&dval, sizeof(dval));
+            DecimalPoint point = node->pn_u.number.decimalPoint;
+            data.append((const char*)&point, sizeof(point));
+            return StringTree::makeLeaf(cx, kind, Move(data));
+        }
+
+        // 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 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]);
+
+    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());
+        }
+
+        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;
+        }
+
+        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);
+
+            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());
+        }
+
+        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);
+
+            UniquePtr<ParseNode> left(treeParse(cx, concat.children[0].get(), debug));
+            UniquePtr<ParseNode> right(treeParse(cx, concat.children[1].get(), debug));
+
+            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);
+
+            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());
+        }
+
+        // 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 cx->new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), treeParse(cx, children[0].get(), debug));
+        }
+
+        // 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();
+        }
+
+        // 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);
+        }
+
+        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();
+        }
+
+        // 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);
+
+            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());
+        }
+    }
+
+    MOZ_CRASH("treeParse: out of switch()");
+    return nullptr;
+}
+
+#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));
+
+        JSString* quoted = QuoteString(cx, atom, 0);
+        JSAutoByteString bs;
+        const char* bytes = bs.encodeLatin1(cx, quoted);
+
+        const size_t size = strlen(bytes); // FIXME: Is this really the size?
+        out.write((const char*)&size, sizeof(size));
+        out.write(bytes, size);
+
+        // fprintf(stderr, "binSerializeAtom %s (%zu)\n", bytes, size);
+        return true;
+    } else {
+        OptionValue kind(OptionIsAbsent);
+        out.write((const char*)&kind, sizeof(kind));
+        return true;
+    }
+}
+
+
+
+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;
+    }
+
+    // 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());
+    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* 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
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinaryAST.h
@@ -0,0 +1,77 @@
+#ifndef frontend_BinaryAST_h
+#define frontend_BinaryAST_h
+
+#include <iostream>
+#include <istream>
+#include <sstream>
+
+#include "frontend/ParseNode.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& out, std::ostringstream& debug);
+
+// A fast-concatenation-of-strings data structure.
+struct StringTree {
+    struct Leaf {
+        const std::string data;
+        Leaf(const std::string&&);
+        Leaf(); // Empty leaf
+    };
+    struct Concat {
+        const mozilla::Vector<UniquePtr<StringTree>> children;
+        Concat(mozilla::Vector<UniquePtr<StringTree>>&&);
+    };
+    enum VariantKind {
+        IsLeaf = 128,
+        IsConcat = 129
+    };
+    const VariantKind variantKind;
+    union Variant {
+        Leaf* leaf;
+        Concat* concat;
+        Variant(Leaf* leaf_):
+            leaf(leaf_)
+        {}
+        Variant(Concat* concat_):
+            concat(concat_)
+        {}
+    };
+    const Variant data;
+    const size_t byteLength;
+    const ParseNodeKind kind;
+
+    // Leaf constructors
+    StringTree(ParseNodeKind, Leaf* leaf);
+    static StringTree* makeLeaf(JSContext* cx, ParseNodeKind);
+    static StringTree* makeLeaf(JSContext* cx, ParseNodeKind, std::string&&);
+    bool isLeaf() const;
+    const Leaf& asLeaf() const;
+
+
+    // Concat constructors
+    StringTree(ParseNodeKind, Concat* concat);
+    static StringTree* makeConcat(JSContext* cx, ParseNodeKind, UniquePtr<StringTree>&& singleChild);
+    static StringTree* makeConcat(JSContext* cx, ParseNodeKind, mozilla::Vector<UniquePtr<StringTree>>&& children);
+
+    bool isConcat() const;
+    const Concat& asConcat() const;
+    const mozilla::Vector<UniquePtr<StringTree>>& children() const;
+
+    static StringTree* read(JSContext* cx, std::istringstream& in);
+    void write(std::ostringstream& out) const;
+
+    ~StringTree();
+};
+
+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 // frontend_BinaryAST_h
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -97,31 +97,18 @@ class NodeStack {
 enum class PushResult { Recyclable, CleanUpLater };
 
 static PushResult
 PushCodeNodeChildren(ParseNode* node, NodeStack* stack)
 {
     MOZ_ASSERT(node->isArity(PN_CODE));
 
     /*
-     * Function nodes are linked into the function box tree, and may appear
-     * on method lists. Both of those lists are singly-linked, so trying to
-     * update them now could result in quadratic behavior when recycling
-     * trees containing many functions; and the lists can be very long. So
-     * we put off cleaning the lists up until just before function
-     * analysis, when we call CleanFunctionList.
      *
-     * In fact, we can't recycle the parse node yet, either: it may appear
-     * on a method list, and reusing the node would corrupt that. Instead,
-     * we clear its pn_funbox pointer to mark it as deleted;
-     * CleanFunctionList recycles it as well.
      *
-     * We do recycle the nodes around it, though, so we must clear pointers
-     * to them to avoid leaving dangling references where someone can find
-     * them.
      */
     node->pn_funbox = nullptr;
     if (node->pn_body)
         stack->push(node->pn_body);
     node->pn_body = nullptr;
 
     return PushResult::CleanUpLater;
 }
@@ -919,8 +906,10 @@ js::frontend::IsAnonymousFunctionDefinit
         return true;
 
     // 14.5.8 (ClassExpression)
     if (pn->is<ClassNode>() && !pn->as<ClassNode>().names())
         return true;
 
     return false;
 }
+
+
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -224,17 +224,17 @@ IsTypeofKind(ParseNodeKind kind)
  *                                    without Initializer
  *                                  PNK_ASSIGN for BindingPattern with
  *                                    Initializer
  *                          followed by:
  *                              PNK_STATEMENTLIST node for function body
  *                                statements,
  *                              PNK_RETURN for expression closure
  *                          pn_count: 1 + number of formal parameters
- *                          pn_tree: PNK_PARAMSBODY or PNK_STATEMENTLIST node
+ *                          pn_tree: PNK_PARAMSBODY or PNK_STATEMENTLIST node // FIXME: What's pn_tree?
  * PNK_SPREAD   unary       pn_kid: expression being spread
  *
  * <Statements>
  * PNK_STATEMENTLIST list   pn_head: list of pn_count statements
  * PNK_IF       ternary     pn_kid1: cond, pn_kid2: then, pn_kid3: else or null.
  *                            In body of a comprehension or desugared generator
  *                            expression, pn_kid2 is PNK_YIELD, PNK_ARRAYPUSH,
  *                            or (if the push was optimized away) empty
@@ -272,17 +272,17 @@ IsTypeofKind(ParseNodeKind kind)
  *                          pn_kid3: null or finally block
  * PNK_CATCHLIST list       pn_head: list of PNK_LEXICALSCOPE nodes, one per
  *                                   catch-block, each with pn_expr pointing
  *                                   to a PNK_CATCH node
  * PNK_CATCH    ternary     pn_kid1: PNK_NAME, PNK_ARRAY, or PNK_OBJECT catch var node
  *                                   (PNK_ARRAY or PNK_OBJECT if destructuring)
  *                          pn_kid2: null or the catch guard expression
  *                          pn_kid3: catch block statements
- * PNK_BREAK    name        pn_atom: label or null
+ * PNK_BREAK    name        pn_atom: label or null // FIXME: loopControl.label, no?
  * PNK_CONTINUE name        pn_atom: label or null
  * PNK_WITH     binary      pn_left: head expr; pn_right: body;
  * PNK_VAR,     list        pn_head: list of PNK_NAME or PNK_ASSIGN nodes
  * PNK_LET,                          each name node has either
  * PNK_CONST                           pn_used: false
  *                                     pn_atom: variable name
  *                                     pn_expr: initializer or null
  *                                   or
@@ -456,16 +456,22 @@ class ParseNode
     bool pn_parens:1;   /* this expr was enclosed in parens */
     bool pn_rhs_anon_fun:1;  /* this expr is anonymous function or class that
                               * is a direct RHS of PNK_ASSIGN or PNK_COLON of
                               * property, that needs SetFunctionName. */
 
     ParseNode(const ParseNode& other) = delete;
     void operator=(const ParseNode& other) = delete;
 
+    static ParseNode* uglyParse(const char*);
+    MOZ_MUST_USE static bool uglySerializeAux(ParseNode* node, mozilla::Vector<char16_t>& out);
+    MOZ_MUST_USE bool uglySerializeAux(mozilla::Vector<char16_t>& out);
+    bool uglySerializeList(mozilla::Vector<char16_t>& out);
+    const char* uglySerialize();
+
   public:
     ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity)
       : pn_type(kind),
         pn_op(op),
         pn_arity(arity),
         pn_parens(false),
         pn_rhs_anon_fun(false),
         pn_pos(0, 0),
@@ -570,17 +576,17 @@ class ParseNode
         struct {
             LexicalScope::Data* bindings;
             ParseNode*          body;
         } scope;
         struct {
             double       value;         /* aligned numeric literal value */
             DecimalPoint decimalPoint;  /* Whether the number has a decimal point */
         } number;
-        class {
+        struct {
             friend class LoopControlStatement;
             PropertyName*    label;    /* target of break/continue statement */
         } loopControl;
     } pn_u;
 
 #define pn_objbox       pn_u.name.objbox
 #define pn_funbox       pn_u.name.funbox
 #define pn_body         pn_u.name.expr
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -13,16 +13,17 @@
  * generates trees of nodes induced by the recursive parsing (not precise
  * syntax trees, see Parser.h).  After tree construction, it rewrites trees to
  * fold constants and evaluate compile-time expressions.
  *
  * This parser attempts no error recovery.
  */
 
 #include "frontend/Parser.h"
+#include "frontend/BinaryAST.h"
 
 #include "mozilla/Sprintf.h"
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsopcode.h"
@@ -953,16 +954,17 @@ TraceParser(JSTracer* trc, AutoGCRooter*
  * Parse a top-level JS script.
  */
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::parse()
 {
     MOZ_ASSERT(checkOptionsCalled);
 
+    fprintf(stderr, "Parser::parse()\n");
     Directives directives(options().strictOption);
     GlobalSharedContext globalsc(context, ScopeKind::Global,
                                  directives, options().extraWarningsOption);
     ParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
     if (!globalpc.init())
         return null();
 
     ParseContext::VarScope varScope(this);
@@ -2158,47 +2160,150 @@ Parser<FullParseHandler>::evalBody(EvalS
 
     return body;
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::globalBody(GlobalSharedContext* globalsc)
 {
+    fprintf(stderr, "Parser::globalBody\n");
+
+    ParseNode* body;
+    const size_t REPEAT = 1;
+    mozilla::TimeDuration totalFromSource;
+
     ParseContext globalpc(this, globalsc, /* newDirectives = */ nullptr);
     if (!globalpc.init())
         return nullptr;
 
     ParseContext::VarScope varScope(this);
     if (!varScope.init(pc))
         return nullptr;
 
-    ParseNode* body = statementList(YieldIsName);
-    if (!body)
-        return nullptr;
-
+    for (size_t i = 0; i < REPEAT; ++i) {
+        if (i >= 1) {
+            tokenStream.reset();
+        }
+
+        mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+        body = statementList(YieldIsName);
+        if (!body)
+            return nullptr;
+        mozilla::TimeStamp stop = mozilla::TimeStamp::Now();
+
+        mozilla::TimeDuration delta = stop - start;
+        totalFromSource += delta;
+    }
     if (!checkStatementsEOF())
         return nullptr;
 
     if (!FoldConstants(context, &body, this))
         return nullptr;
 
     Maybe<GlobalScope::Data*> bindings = newGlobalScopeData(pc->varScope());
     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 debug;
+    UniquePtr<StringTree> tree(treeSerialize(context, body, debug));
+    if (!tree) {
+        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();
+
+    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);
+
+        mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+        UniquePtr<StringTree> parsed_tree(StringTree::read(context, in_tree));
+        UniquePtr<ParseNode> body2(treeParse(context, parsed_tree.get(), trace));
+
+        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();
+            size_t i;
+            for (i = 0; i < debug.length() && i < witness.length(); ++i) {
+                if (debug[i] != witness[i]) {
+                    break;
+                }
+            }
+            if (i < debug.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";
+                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) {
+            MOZ_CRASH("Re-serialization failed");
+        }
+
+        const auto debug = sanity_debug.str();
+        if (debug.compare(witness) != 0) {
+            size_t i;
+            for (i = 0; i < debug.length() && i < witness.length(); ++i) {
+                if (debug[i] != witness[i]) {
+                    break;
+                }
+            }
+            if (i < debug.length() || i < witness.length()) {
+                std::cerr << "Reserialization doesn't match\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";
+                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));
+
     return body;
 }
 
 template <>
 ParseNode*
 Parser<FullParseHandler>::moduleBody(ModuleSharedContext* modulesc)
 {
     MOZ_ASSERT(checkOptionsCalled);
+    fprintf(stderr, "Parser::moduleBody\n");
 
     ParseContext modulepc(this, modulesc, nullptr);
     if (!modulepc.init())
         return null();
 
     ParseContext::VarScope varScope(this);
     if (!varScope.init(pc))
         return nullptr;
@@ -3392,16 +3497,18 @@ Parser<FullParseHandler>::trySyntaxParse
                                                       GeneratorKind generatorKind,
                                                       FunctionAsyncKind asyncKind,
                                                       bool tryAnnexB,
                                                       Directives inheritedDirectives,
                                                       Directives* newDirectives)
 {
     // Try a syntax parse for this inner function.
     do {
+        break; // FIXME: For testing purposes, make sure that we always full parse.
+
         // If we're assuming this function is an IIFE, always perform a full
         // parse to avoid the overhead of a lazy syntax-only parse. Although
         // the prediction may be incorrect, IIFEs are common enough that it
         // pays off for lots of code.
         if (pn->isLikelyIIFE() && generatorKind == NotGenerator && asyncKind == SyncFunction)
             break;
 
         Parser<SyntaxParseHandler>* parser = handler.syntaxParser;
@@ -7282,17 +7389,16 @@ Parser<ParseHandler>::variableStatement(
     return vars;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::statement(YieldHandling yieldHandling)
 {
     MOZ_ASSERT(checkOptionsCalled);
-
     if (!CheckRecursionLimit(context))
         return null();
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
 
     switch (tt) {
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -247,16 +247,26 @@ TokenStreamBase::reservedWordToPropertyN
       FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
 #undef EMIT_CASE
       default:
         MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind.");
     }
     return nullptr;
 }
 
+void TokenStream::SourceCoords::reset()
+{
+    lineStartOffsets_.clear();
+    lastLineIndex_ = 0;
+    uint32_t maxPtr = MAX_PTR;
+    lineStartOffsets_.infallibleAppend(0);
+    lineStartOffsets_.infallibleAppend(maxPtr);
+}
+
+
 TokenStream::SourceCoords::SourceCoords(JSContext* cx, uint32_t ln)
   : lineStartOffsets_(cx), initialLineNum_(ln), lastLineIndex_(0)
 {
     // This is actually necessary!  Removing it causes compile errors on
     // GCC and clang.  You could try declaring this:
     //
     //   const uint32_t TokenStream::SourceCoords::MAX_PTR;
     //
@@ -421,16 +431,29 @@ TokenStreamBase::TokenStreamBase(JSConte
     displayURL_(nullptr),
     sourceMapURL_(nullptr),
     cx(cx),
     mutedErrors(options.mutedErrors()),
     strictModeGetter(smg)
 {
 }
 
+void
+TokenStream::reset()
+{
+    srcCoords.reset();
+    cursor = 0;
+    lookahead = 0;
+    lineno = options().lineno;
+    linebase = 0;
+    prevLinebase = size_t(-1);
+    displayURL_ = nullptr;
+    sourceMapURL_ = nullptr;
+}
+
 TokenStream::TokenStream(JSContext* cx, const ReadOnlyCompileOptions& options,
                          const char16_t* base, size_t length, StrictModeGetter* smg)
   : TokenStreamBase(cx, options, smg),
     userbuf(cx, base, length, options.column),
     tokenbuf(cx)
 {
     // Nb: the following tables could be static, but initializing them here is
     // much easier.  Don't worry, the time to initialize them for each
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -33,17 +33,17 @@ struct KeywordInfo;
 
 namespace js {
 namespace frontend {
 
 struct TokenPos {
     uint32_t    begin;  // Offset of the token's first char.
     uint32_t    end;    // Offset of 1 past the token's last char.
 
-    TokenPos() {}
+    TokenPos() : begin(0), end(0) {}
     TokenPos(uint32_t begin, uint32_t end) : begin(begin), end(end) {}
 
     // Return a TokenPos that covers left, right, and anything in between.
     static TokenPos box(const TokenPos& left, const TokenPos& right) {
         MOZ_ASSERT(left.begin <= left.end);
         MOZ_ASSERT(left.end <= right.begin);
         MOZ_ASSERT(right.begin <= right.end);
         return TokenPos(left.begin, right.end);
@@ -391,16 +391,22 @@ class TokenStreamBase
         bool isDirtyLine:1;     // Non-whitespace since start of line.
         bool sawOctalEscape:1;  // Saw an octal character escape.
         bool hadError:1;        // Hit a syntax error, at start or during a
                                 // token.
 
         Flags()
           : isEOF(), isDirtyLine(), sawOctalEscape(), hadError()
         {}
+        void reset() {
+            isEOF = false;
+            isDirtyLine = false;
+            sawOctalEscape = false;
+            hadError = false;
+        }
     };
 
   public:
     typedef Token::Modifier Modifier;
     static constexpr Modifier None = Token::None;
     static constexpr Modifier Operand = Token::Operand;
     static constexpr Modifier TemplateTail = Token::TemplateTail;
 
@@ -537,16 +543,17 @@ class TokenStreamBase
 
         static const uint32_t MAX_PTR = UINT32_MAX;
 
         uint32_t lineIndexToNum(uint32_t lineIndex) const { return lineIndex + initialLineNum_; }
         uint32_t lineNumToIndex(uint32_t lineNum)   const { return lineNum   - initialLineNum_; }
 
       public:
         SourceCoords(JSContext* cx, uint32_t ln);
+        void reset();
 
         MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset);
         MOZ_MUST_USE bool fill(const SourceCoords& other);
 
         bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
             uint32_t lineIndex = lineNumToIndex(lineNum);
             if (lineIndex + 1 >= lineStartOffsets_.length()) // +1 due to sentinel
                 return false;
@@ -645,17 +652,17 @@ class TokenStreamBase
 //
 class MOZ_STACK_CLASS TokenStream final : public TokenStreamBase
 {
   public:
     using CharBuffer = Vector<char16_t, 32>;
 
     TokenStream(JSContext* cx, const ReadOnlyCompileOptions& options,
                 const char16_t* base, size_t length, StrictModeGetter* smg);
-
+    void reset();
     MOZ_MUST_USE bool checkOptions();
 
     const CharBuffer& getTokenbuf() const { return tokenbuf; }
 
     // If there is an invalid escape in a template, report it and return false,
     // otherwise return true.
     bool checkForInvalidTemplateEscapeError() {
         if (invalidTemplateEscapeType == InvalidEscapeType::None)
@@ -952,16 +959,22 @@ class MOZ_STACK_CLASS TokenStream final 
       public:
         TokenBuf(JSContext* cx, const char16_t* buf, size_t length, size_t startOffset)
           : base_(buf),
             startOffset_(startOffset),
             limit_(buf + length),
             ptr(buf)
         { }
 
+        bool reset(size_t startOffset) {
+            ptr = base_;
+            startOffset_ = startOffset;
+            return true;
+        }
+
         bool hasRawChars() const {
             return ptr < limit_;
         }
 
         bool atStart() const {
             return offset() == 0;
         }
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -162,16 +162,17 @@ UNIFIED_SOURCES += [
     'builtin/TestingFunctions.cpp',
     'builtin/TypedObject.cpp',
     'builtin/WeakMapObject.cpp',
     'builtin/WeakSetObject.cpp',
     'devtools/sharkctl.cpp',
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
+    'frontend/BinaryAST.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeEmitter.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/NameFunctions.cpp',
     'frontend/ParseNode.cpp',
     'frontend/TokenStream.cpp',
     'gc/Allocator.cpp',
     'gc/AtomMarking.cpp',