Bug 1349917 - Early implementation of a binary format draft
authorDavid Teller <dteller@mozilla.com>
Wed, 19 Apr 2017 12:33:35 +0200
changeset 567759 f0a619f98c696a62916b00ed4a88852f5e11fd81
parent 567252 62a2d3693579fc77b1c510984ae471a860d03302
child 567760 0f8dc8c27dd305d3a5eb6d9ffee5c5b4da843b12
push id55689
push userdteller@mozilla.com
push dateTue, 25 Apr 2017 14:21:34 +0000
bugs1349917
milestone55.0a1
Bug 1349917 - Early implementation of a binary format MozReview-Commit-ID: IaZLgj4ohff
js/src/frontend/BinaryAST.cpp
js/src/frontend/BinaryAST.h
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinaryAST.cpp
@@ -0,0 +1,1021 @@
+// ---- 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 {
+
+
+/**
+ * A marker for presence/absence.
+ */
+enum OptionValue {
+    OptionIsHere = -1,
+    OptionIsAbsent = -2,
+};
+
+// 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()
+        + (sizeof StringTree::variantKind);
+    for (const UniquePtr<StringTree>& item: concat->children) {
+        total
+         += sizeof HEADER
+         + sizeof item->kind
+         + sizeof item->byteLength
+         + item->byteLength
+         + sizeof FOOTER;
+    }
+    return total;
+}
+
+size_t totalByteLength(const StringTree::Leaf* leaf) {
+    return leaf->data.length()
+        + sizeof StringTree::variantKind;
+}
+
+// FIXME: We should rather have a StringTreeWriter.
+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");
+    }
+
+    size_t byteLength;
+    in.read((char*)&byteLength, sizeof byteLength);
+
+    VariantKind variant;
+    in.read((char*)&variant, sizeof variant);
+
+    MOZ_ASSERT(variant == VariantKind::IsLeaf || variant == VariantKind::IsConcat);
+
+    const size_t byteStart = in.tellg();
+
+    UniquePtr<StringTree> result;
+
+    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 + 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);
+
+    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);
+        }
+    }
+    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); // FIXME: Useless copy.
+
+    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("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;
+}
+
+MOZ_MUST_USE StringTree*
+treeSerialize(JSContext* cx, const ParseNode* node, std::ostringstream& debug)
+{
+    const ParseNodeKind kind = node ? node->getKind() : PNK_LIMIT;
+//    std::cerr << "treeSerialize " << NAMES[kind] << "\n";
+    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: {
+            const LoopControlStatement* cast = reinterpret_cast<const LoopControlStatement*>(node);
+            std::string label;
+            serializeAtom(cx, cast->label(), label);
+            return StringTree::makeLeaf(cx, kind, Move(label));
+        }
+        case PNK_LABEL: {
+            const LabeledStatement* cast = reinterpret_cast<const LabeledStatement*>(node);
+            std::string label;
+            serializeAtom(cx, cast->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, PNK_DOT, 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: {
+            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.");
+}
+
+class TreeParser MOZ_RAII {
+public:
+    TreeParser(JSContext* cx_, ParseNodeAllocator& alloc, std::istringstream& in_, std::ostringstream& debug_)
+        : cx(cx_)
+        , in(in_)
+        , debug(debug_)
+        , functionDepth(0)
+        , allocator(alloc)
+    { }
+    ~TreeParser() {
+        MOZ_ASSERT(in.peek() == std::char_traits<char>::eof());
+        MOZ_ASSERT(functionDepth == 0);
+    }
+
+    // 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);
+
+        const size_t byteStart = in.tellg();
+        readNodeAsLeaf(buf);
+        readFooter(byteStart, byteLength);
+    }
+
+    // Parse a subtree as a ParseNode.
+    MOZ_MUST_USE ParseNode* parseNode()
+    {
+        ParseNodeKind kind;
+        size_t byteLength;
+        readHeader(kind, byteLength);
+
+        const size_t byteStart = in.tellg();
+
+        // Actually parse subtree.
+        UniquePtr<ParseNode> result(parseNode(kind));
+
+        readFooter(byteStart, byteLength);
+        return result.release();
+    }
+
+    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);
+
+        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);
+
+        StringTree::VariantKind variant;
+        in.read((char*)&variant, sizeof variant);
+        MOZ_ASSERT(variant == StringTree::VariantKind::IsConcat);
+
+        size_t length;
+        in.read((char*)&length, sizeof length);
+
+        return length;
+    }
+    ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
+private:
+    JSContext* cx;
+    std::istringstream& in;
+    std::ostringstream& debug;
+    size_t functionDepth;
+
+    JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
+    ParseNodeAllocator& allocator;
+    ParseNode* allocParseNode(size_t size) {
+        MOZ_ASSERT(size == sizeof(ParseNode));
+        return static_cast<ParseNode*>(allocator.allocNode());
+    }
+
+    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");
+        }
+
+        in.read((char*)&kind, sizeof kind);
+        if (kind >= mozilla::ArrayLength(NAMES)) {
+            MOZ_CRASH("Bad kind");
+        }
+
+        // Lookahead bytelength
+        in.read((char*)&byteLength, sizeof byteLength);
+        in.seekg(-sizeof byteLength, std::ios_base::seekdir::cur);
+    }
+
+    void readFooter(const size_t byteStart, const size_t byteLength) {
+        // Check byteLength, footer
+        const size_t byteStop = in.tellg();
+
+        MOZ_ASSERT(byteStop - byteStart == byteLength + sizeof byteLength);
+
+        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_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 new_<LexicalScopeNode>(nullptr, body.release());
+            }
+
+            case PNK_FUNCTION: {
+                ++functionDepth;
+                const size_t length = readNodeAsConcat();
+                MOZ_ASSERT(length == 1);
+
+                UniquePtr<ParseNode> body(parseNode());
+                MOZ_ASSERT(body);
+
+                CodeNode* result(new_<CodeNode>(kind, JSOP_NOP, TokenPos()));
+                result->pn_body = body.release();
+                --functionDepth;
+                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 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: 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());
+
+                if (kind == PNK_CASE) {
+                    return new_<CaseClause>(left.release(), right.release(), 0);
+                } else if (kind == PNK_FORIN || kind == PNK_FOROF) {
+                    return new_<TernaryNode>(kind, JSOP_NOP, left.release(), nullptr, right.release());
+                } else {
+                    return 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 new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), parseNode());
+            }
+
+            // 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(new_<ListNode>(kind, TokenPos()));
+
+                for (uint32_t i = 0; i < length; ++i) {
+                    ParseNode* current = parseNode();
+                    if (i == 0) {
+                        result->pn_head = current;
+                        result->pn_tail = &result->pn_head;
+                    } else {
+                        latest->pn_next = current;
+                        result->pn_tail  = &latest->pn_next;
+                    }
+                    latest = current;
+                    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 new_<BreakStatement>(label, TokenPos());
+                } else {
+                    return new_<ContinueStatement>(label, TokenPos());
+                }
+            }
+
+            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 new_<LabeledStatement>(label, expr.release(), 0);
+                } else {
+                  return 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));
+                if (kind == PNK_NAME) {
+                    return new_<NameNode>(kind, JSOP_NOP, atom, TokenPos());
+                } else {
+                    return 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(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 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 new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
+            }
+
+            // Stuff we don't handle yet:
+            case PNK_REGEXP: {
+                std::string leaf;
+                readNodeAsLeaf(leaf);
+
+                return 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) {
+    const UniquePtr<StringTree> tree(treeSerialize(cx, node, debug));
+    if (!tree) {
+        return false;
+    }
+    tree->write(out);
+    std::cerr << "binSerialize produced " << out.tellp() << " bytes\n";
+    return true;
+}
+MOZ_MUST_USE ParseNode* binParse(JSContext* cx, ParseNodeAllocator& alloc, std::istringstream& in, std::ostringstream& debug) {
+    TreeParser parser(cx, alloc, in, debug);
+    return parser.parseNode();
+}
+
+} // namespace frontend
+} // namespace js
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinaryAST.h
@@ -0,0 +1,72 @@
+#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, ParseNodeAllocator& alloc, 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();
+};
+
+}
+}
+
+#endif // frontend_BinaryAST_h
--- 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',