Bug 1349917 - Extracting all strings to a strings table draft
authorDavid Teller <dteller@mozilla.com>
Fri, 21 Apr 2017 22:45:22 +0200
changeset 567762 d4db1ee1b5b3faf894417ba1d13a1ccb9a43f5e2
parent 567761 6d506951a416bc051448abe6cf8b4fad9b938d49
child 567763 6e4b9a97f7b7e13e2c8383ed1efc0891456c90c6
push id55689
push userdteller@mozilla.com
push dateTue, 25 Apr 2017 14:21:34 +0000
bugs1349917
milestone55.0a1
Bug 1349917 - Extracting all strings to a strings table All strings (string literals, identifiers, templates) are now extracted to a table and represented inline as a (variable-length) index in the table. The layout of the file now looks as follows: 1. (variable length) number of strings; 2. array of (variable length) length of strings; 3. blob containing all strings; 4. actual encoding of the tree. The representation of the strings table as 1+2+3 is designed to let us read strings lazily should we decide to. For this prototype, the strings are laid out before the tree in the hope that we can start atomizing them in the background before we have finished receiving the script source. With this encoding, we manage the following scores (gzipped sizes): - DevTools snapshot *0.38 - Facebook snapshot *0.96 MozReview-Commit-ID: FoFhAOEHSED
js/src/frontend/BinaryAST.cpp
js/src/frontend/BinaryAST.h
--- a/js/src/frontend/BinaryAST.cpp
+++ b/js/src/frontend/BinaryAST.cpp
@@ -1,49 +1,57 @@
 // ---- Ongoing experiment: binary ast serializer/parser
 
+#include <algorithm>
 #include <iostream>
 #include <istream>
 #include <sstream>
 #include <strstream>
+#include <unordered_map>
+#include <vector>
 
 #include "mozilla/Attributes.h"
 #include "frontend/BinaryAST.h"
 #include "frontend/ParseNode.h"
 #include "jsatom.h"
 #include "jsopcode.h"
 
 namespace js {
 namespace frontend {
 
 // #define DEBUG_HEADER 1
 
 using VariantKind = StringTree::VariantKind;
+using StringTable = std::unordered_map<std::string, uint32_t>;
+using Occurrences = std::pair<std::string, uint32_t>;
 
-/**
- * A marker for presence/absence.
- */
-enum OptionValue {
-    OptionIsHere = -1,
-    OptionIsAbsent = -2,
+bool compareOccurrences(const Occurrences& left, const Occurrences& right) {
+    return left.second < right.second;
 };
 
 // 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"
 };
 
+void writeUnsigned(std::ostringstream& out, uint32_t value)
+{
+    jit::CompactBufferWriter writer;
+    writer.writeUnsigned(value);
+    out.write((const char*)writer.buffer(), writer.length());
+}
+
 // FIXME: We should rather have a StringTreeWriter.
 StringTree::~StringTree()
 {
    switch (variantKind) {
        case VariantKind::IsLeaf:
            delete data.leaf;
            return;
        case VariantKind::IsConcat:
@@ -55,45 +63,47 @@ StringTree::~StringTree()
 
 StringTree::StringTree(ParseNodeKind k, Leaf* leaf)
     : variantKind(VariantKind::IsLeaf)
     , data(leaf)
     , kind(k)
 {}
 
 StringTree::Leaf::Leaf()
-    : data("")
+    : data()
 { }
 
 StringTree::Leaf::Leaf(const std::string&& data_)
-    : data(data_)
+    : data(Move(data_))
 { }
 
+
 /*static*/ StringTree*
 StringTree::makeLeaf(JSContext* cx, ParseNodeKind kind)
 {
-    return cx->new_<StringTree>(kind, cx->new_<StringTree::Leaf>());
+    UniquePtr<Leaf> leaf(cx->new_<StringTree::Leaf>());
+    return cx->new_<StringTree>(kind, leaf.release());
 }
 
 /*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&
+const std::string&
 StringTree::asLeaf() const {
     MOZ_ASSERT(isLeaf());
-    return *data.leaf;
+    return data.leaf->data;
 }
 
 StringTree::StringTree(ParseNodeKind k, Concat* concat)
     : variantKind(VariantKind::IsConcat)
     , data(concat)
     , kind(k)
 {}
 
@@ -125,109 +135,33 @@ StringTree::asConcat() const {
     return *data.concat;
 }
 
 const mozilla::Vector<UniquePtr<StringTree>>&
 StringTree::children() const {
     return asConcat().children;
 }
 
-void
-StringTree::write(std::ostringstream& out) const {
-    // std::cerr << "StringTree::write at " << out.tellp() << "\n";
-#if DEBUG_HEADER
-    // std::cerr << "StringTree::write HEADER at " << out.tellp() << "\n";
-    out.write(HEADER, sizeof HEADER - 1);
-#endif // DEBUG_HEADER
-
-    // std::cerr << "StringTree::write kind at " << out.tellp() << "\n";
-    out.write((const char*)&kind, sizeof kind);
-
-    // std::cerr << "StringTree::write " << NAMES[kind] << "\n";
-    if (kind != PNK_LIMIT) {
-
-#if DEBUG_HEADER
-        // std::cerr << "StringTree::write variantKind at " << out.tellp() << "\n";
-        const unsigned char char_variant = variantKind;
-        out.write((const char*)&char_variant, sizeof char_variant);
-        MOZ_ASSERT(variantKind == VariantKind::IsLeaf || variantKind == VariantKind::IsConcat);
-#endif // DEBUG_HEADER
-
-        std::ostringstream sub_out;
-        if (isLeaf()) {
-            const Leaf& leaf = asLeaf();
-
-            const size_t byteLength = leaf.data.length();
-
-            jit::CompactBufferWriter lengthWriter;
-            lengthWriter.writeUnsigned((uint32_t)byteLength);
-            out.write((const char*)lengthWriter.buffer(), lengthWriter.length());
-
-            const size_t byteStart = out.tellp();
-            out << leaf.data;
-            const size_t byteStop = out.tellp();
-            MOZ_ASSERT(byteLength == byteStop - byteStart);
-
-        } else {
-            const Concat& concat = asConcat();
-
-            {
-                jit::CompactBufferWriter lengthWriter;
-                lengthWriter.writeUnsigned((uint32_t)concat.children.length());
-                sub_out.write((const char*)lengthWriter.buffer(), lengthWriter.length());
-                for (const UniquePtr<StringTree>& tree: concat.children) {
-                    tree->write(sub_out);
-                }
-            }
-
-            const std::string sub_str = sub_out.str();
-            const size_t byteLength = sub_str.length();
-            {
-                jit::CompactBufferWriter lengthWriter;
-                lengthWriter.writeUnsigned((uint32_t)byteLength);
-
-                // std::cerr << "StringTree::write byteLength at " << out.tellp() << "\n";
-                out.write((const char*)lengthWriter.buffer(), lengthWriter.length());
-
-                // std::cerr << "StringTree::write concat byteLength " << byteLength << "\n";
-                // std::cerr << "StringTree::write concat length " << concat.children.length() << "\n";
-            }
-
-
-            const size_t byteStart = out.tellp();
-            out << sub_str;
-            const size_t byteStop = out.tellp();
-            // std::cerr << "StringTree::write length at " << byteStart << "\n";
-            MOZ_ASSERT(byteLength == byteStop - byteStart);
-        }
-    }
-
-
-#if DEBUG_HEADER
-    out.write(FOOTER, sizeof FOOTER - 1);
-#endif // DEBUG_HEADER
-}
-
-
 using Leaf = StringTree::Leaf;
 using Concat = StringTree::Concat;
 
 void serializeAtom(JSContext* cx, JSAtom* atom, std::string& result)
 {
     std::ostringstream out;
     if (atom) {
         RootedString string(cx, atom);
         JSAutoByteString bs;
         if (!bs.encodeUtf8(cx, string)) { // 0 terminated, right?
             MOZ_CRASH();
         }
 
         out.write(bs.ptr(), bs.length());
     } else {
-        const char zero[2] = { 0, 0 };
+        // FIXME: We could magically map this to string identifier 0. Is it worth it?
+        const char zero[2] = { 0, 0 }; // Magic constant (invalid utf8 string).
         out.write((const char*)&zero, sizeof zero);
     }
     result = Move(out.str());
 }
 
 JSAtom* deserializeAtom(JSContext* cx, const std::string& data)
 {
     // Special constant '\0\0' for empty atoms.
@@ -246,337 +180,544 @@ bool deserializePropertyName(JSContext* 
     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] << " ";
+// FIXME: We could make it a bit smaller by sorting strings to ensure that the
+// ones used most often have a shorter variable-length id.
+class TreeSerializer {
+private:
+    JSContext* cx;
+    std::ostringstream& debug;
+    std::unordered_map<std::string, uint32_t> strings;
 
-    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)))) {
+    uint32_t getStringId(const std::string& data) const {
+        const uint32_t result = strings.find(data)->second;
+        return result;
+#if 0
+        const auto search = strings.find(data);
+        if (search != strings.end()) {
+            return search->second;
+        } else {
+            const uint32_t key = ++string_counter;
+            if (!strings.insert({data, key}).second) {
                 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));
+            return key;
+        }
+#endif // 0
+    }
+public:
+    const StringTable getStringTable() const {
+        return this->strings;
+    }
+    TreeSerializer(JSContext* cx_, std::ostringstream& debug_)
+        : cx(cx_)
+        , debug(debug_)
+    {
+    }
+
+    MOZ_MUST_USE bool treeSerialize(const ParseNode* node, std::ostringstream& out)
+    {
+        UniquePtr<StringTree> tree(treeSerializeAux(node));
+        if (!tree) {
+            return false;
         }
 
-        // 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));
+        // Find number of occurrences of each string.
+        std::unordered_map<std::string, uint32_t> occurrences;
+        computeOccurrences(tree.get(), occurrences);
+
+        // Sort by number of occurrences.
+        std::vector<Occurrences> heap;
+        for (auto iter: occurrences) {
+            heap.push_back(iter);
+        }
+        std::make_heap(heap.begin(), heap.end(), compareOccurrences); // FIXME: Check order.
+
+        std::unordered_map<std::string, uint32_t> strings;
+
+        // Assign an identifier to each string
+        uint32_t i = 0;
+        for (auto iter: heap) {
+            strings.insert({iter.first, i});
+            ++i;
+        }
+        this->strings = Move(strings);
+
+        // Prepare and write lengths table
+        std::vector<uint32_t> lengths;
+        for (auto iter: heap) {
+            lengths.push_back(iter.first.length());
         }
 
-        // 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));
+        writeUnsigned(out, lengths.size());
+        size_t totalLength = 0;
+        for (auto iter: lengths) {
+            writeUnsigned(out, iter);
+            totalLength += iter;
         }
 
-        // 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));
+        // std::cerr << "treeSerialize: writing " << lengths.size() << " strings, for a total length of " << totalLength << "\n";
+
+        // Now write strings in order.
+        // To extract a string, you first need to have read the lengths table.
+        MOZ_ASSERT(heap.size() == lengths.size());
+        const size_t startStrings = out.tellp();
+        for (auto iter: heap) {
+            out.write(&iter.first[0], iter.first.length());
         }
 
-        // 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;
+        MOZ_ASSERT((size_t)out.tellp() == (size_t)(startStrings + totalLength));
+
+        // Finally, write tree
+        write(tree.get(), out);
+
+        return true;
+    }
+
+private:
+    void computeOccurrences(StringTree* tree, std::unordered_map<std::string, uint32_t>& occurrences) {
+        if (tree->isLeaf()) {
+            const std::string& data = tree->asLeaf();
+            auto search = occurrences.find(data);
+            if (search != occurrences.end()) {
+                search->second++;
+            } else {
+                occurrences.insert({data, 1});
+            }
+            return;
+        }
+        const Concat& concat = tree->asConcat();
+        for (const UniquePtr<StringTree>& tree: concat.children) {
+            computeOccurrences(tree.get(), occurrences);
+        }
+    }
+
+    MOZ_MUST_USE StringTree* treeSerializeAux(const ParseNode* node)
+    {
+        const ParseNodeKind kind = node ? node->getKind() : PNK_LIMIT;
+    //    // std::cerr << "treeSerialize " << NAMES[kind] << "\n";
+        debug << NAMES[kind] << " ";
 
-            for (ParseNode* statement = node->pn_head; statement != nullptr; statement = statement->pn_next) {
-                if (!children.append(treeSerialize(cx, statement, debug))) {
+        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(treeSerializeAux(node->pn_body));
+                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>(treeSerializeAux(node->pn_kid1)))) {
+                    MOZ_CRASH();
+                }
+                if (!children.append(UniquePtr<StringTree>(treeSerializeAux(node->pn_kid2)))) {
+                    MOZ_CRASH();
+                }
+                if (!children.append(UniquePtr<StringTree>(treeSerializeAux(node->pn_kid3)))) {
+                    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>(treeSerializeAux(node->pn_left))))) {
+                    MOZ_CRASH();
+                }
+                if (!children.append(Move(UniquePtr<StringTree>(treeSerializeAux(node->pn_right))))) {
+                    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>(treeSerializeAux(node->pn_kid1)))) {
+                    MOZ_CRASH();
+                }
+                if (!children.append(UniquePtr<StringTree>(treeSerializeAux(node->pn_kid3)))) {
                     MOZ_CRASH();
                 }
+                return StringTree::makeConcat(cx, kind, Move(children));
             }
-            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(treeSerializeAux(node->pn_kid));
+                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(treeSerializeAux(statement))) {
+                        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(treeSerializeAux(node->pn_expr))) {
+                    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(treeSerializeAux(node->pn_expr))) {
+                    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]);
+                std::ostringstream placeholder("/Placeholder regexp/");
+                placeholder << debug.tellp() << "/g"; // FIXME: Just a way to make each regexp unique.
+                return StringTree::makeLeaf(cx, kind, Move(placeholder.str()));
+            }
+        }
+        MOZ_CRASH("Shouldn't reach that point.");
+    }
+
+public:
+    void write(StringTree* tree, std::ostringstream& out) const {
+        // std::cerr << "StringTree::write at " << out.tellp() << "\n";
+    #if DEBUG_HEADER
+        // std::cerr << "StringTree::write HEADER at " << out.tellp() << "\n";
+        out.write(HEADER, sizeof HEADER - 1);
+    #endif // DEBUG_HEADER
+
+        // std::cerr << "StringTree::write kind at " << out.tellp() << "\n";
+        out.write((const char*)&tree->kind, sizeof tree->kind);
+
+        // std::cerr << "StringTree::write " << NAMES[tree->kind] << "\n";
+        if (tree->kind != PNK_LIMIT) {
+
+    #if DEBUG_HEADER
+            // std::cerr << "StringTree::write variantKind at " << out.tellp() << "\n";
+            const unsigned char char_variant = tree->variantKind;
+            out.write((const char*)&char_variant, sizeof char_variant);
+            MOZ_ASSERT(tree->variantKind == VariantKind::IsLeaf || tree->variantKind == VariantKind::IsConcat);
+    #endif // DEBUG_HEADER
+
+            if (tree->isLeaf()) {
+                // A leaf is a simple integer, so no byteLength.
+                const uint32_t id = getStringId(tree->asLeaf());
+                writeUnsigned(out, id);
+            } else {
+                std::ostringstream sub_out;
+
+                const Concat& concat = tree->asConcat();
+                writeUnsigned(sub_out, concat.children.length());
+                for (const UniquePtr<StringTree>& tree: concat.children) {
+                    write(tree.get(), sub_out);
+                }
+
+                const std::string sub_str = sub_out.str();
+                const size_t byteLength = sub_str.length();
+                writeUnsigned(out, byteLength);
+                MOZ_ASSERT(byteLength != 0);
+
+                const size_t byteStart = out.tellp();
+                out << sub_str;
+                const size_t byteStop = out.tellp();
+                // std::cerr << "StringTree::write length at " << byteStart << "\n";
+                MOZ_ASSERT(byteLength == byteStop - byteStart);
+            }
         }
 
-        // 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/");
-        }
+    #if DEBUG_HEADER
+        out.write(FOOTER, sizeof FOOTER - 1);
+    #endif // DEBUG_HEADER
     }
-    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)
         , numberOfNodes(0)
+        , currentByteLength((size_t)-1)
+        , currentByteStart((size_t)-1)
+        , currentKind((ParseNodeKind)-1)
+        , currentVariant((VariantKind)-1)
         , allocator(alloc)
     {
         // std::cerr << "TreeParser starts with offset " << in.tellg() << "\n";
     }
     ~TreeParser() {
         MOZ_ASSERT(in.peek() == std::char_traits<char>::eof());
         MOZ_ASSERT(functionDepth == 0);
         // std::cerr << "TreeParser complete after " << numberOfNodes << " nodes\n";
     }
+    ParseNode* parseNode() {
+        // Read table of lengths of strings
+        const uint32_t numberOfStrings = readVariableLength();
+        if (!lengthsTable.initCapacity(numberOfStrings)) {
+            MOZ_CRASH();
+        }
+        for (uint32_t i = 0; i < numberOfStrings; ++i) {
+            if (!lengthsTable.append(readVariableLength())) {
+                MOZ_CRASH();
+            }
+        }
+
+        // Read table of actual strings. Could be read lazily.
+        if (!stringsTable.initCapacity(numberOfStrings)) {
+            MOZ_CRASH();
+        }
+        for (const auto iter: lengthsTable) {
+            std::string data;
+            data.reserve(iter);
+            in.read(&data[0], iter);
+            if (!stringsTable.append(Move(data))) {
+                MOZ_CRASH();
+            }
+        }
+
+        return parseNodeAux();
+    }
+
+private:
 
     // 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)
+    uint32_t parseLeaf(ParseNodeKind& kind)
     {
-        // std::cerr << "parseBuf\n";
+        // std::cerr << "parseLeaf\n";
 
         const size_t previousByteLength = currentByteLength;
         const size_t previousByteStart  = currentByteStart;
         const ParseNodeKind previousKind = currentKind;
+
+        // Debugging values.
+        currentByteLength = -1;
+        currentByteStart = -1;
+        currentKind = (ParseNodeKind)-1;
+
 #if DEBUG_HEADER
         const VariantKind previousVariant = currentVariant;
 #endif // DEBUG_HEADER
         handleHeader();
 #if DEBUG_HEADER
         MOZ_ASSERT(currentVariant == VariantKind::IsLeaf);
 #endif
         kind = currentKind;
         MOZ_ASSERT(currentKind != PNK_LIMIT);
 
-        readNodeAsLeaf(buf);
+        const uint32_t leaf = readNodeAsLeaf();
         handleFooter();
 
         currentByteLength = previousByteLength;
         currentByteStart = previousByteStart;
         currentKind = previousKind;
 #if DEBUG_HEADER
         currentVariant = previousVariant;
 #endif // DEBUG_HEADER
+
+        return leaf;
     }
 
     // Parse a subtree as a ParseNode.
-    MOZ_MUST_USE ParseNode* parseNode()
+    MOZ_MUST_USE ParseNode* parseNodeAux()
     {
         // std::cerr << "parseNode " << in.tellg() << "\n";
 
         const size_t previousByteLength = currentByteLength;
         const size_t previousByteStart  = currentByteStart;
         const ParseNodeKind previousKind = currentKind;
 #if DEBUG_HEADER
         const VariantKind previousVariant = currentVariant;
@@ -595,33 +736,43 @@ public:
 #if DEBUG_HEADER
         currentVariant = previousVariant;
 #endif // DEBUG_HEADER
 
         return result.release();
     }
 
     // Note: This should NOT be called if the kind is PNK_LIMIT.
-    void readNodeAsLeaf(std::string& buf)
+    uint32_t readNodeAsLeaf()
     {
-        // std::cerr << "readNodeAsLeaf\n";
+        MOZ_ASSERT(currentKind != PNK_LIMIT);
 #if DEBUG_HEADER
         MOZ_ASSERT(currentVariant == VariantKind::IsLeaf);
+#else
+        currentVariant = VariantKind::IsLeaf;
 #endif
+        currentByteStart = -1; // Ignored for leaves.
 
-        buf.resize(currentByteLength);
-        in.read(&buf[0], currentByteLength);
+        return readVariableLength();
     }
 
     size_t readNodeAsConcat() {
+        MOZ_ASSERT(currentKind != PNK_LIMIT);
 #if DEBUG_HEADER
         MOZ_ASSERT(currentVariant == VariantKind::IsConcat);
+#else
+        currentVariant = VariantKind::IsConcat;
 #endif
 
         // std::cerr << "readNodeAsConcat length at " << in.tellg() << "\n";
+        currentByteLength = readVariableLength();
+        MOZ_ASSERT(currentByteLength > 0);
+
+        currentByteStart = in.tellg();
+
         const size_t length = readVariableLength();
 
         // std::cerr << "readNodeAsConcat: length " << length << "\n";
         return length;
     }
     ParseNode* freeTree(ParseNode* pn) {
         return allocator.freeTree(pn);
     }
@@ -630,20 +781,19 @@ private:
     JSContext* cx;
     std::istringstream& in;
     std::ostringstream& debug;
     size_t functionDepth;
     size_t numberOfNodes;
     size_t currentByteLength;
     size_t currentByteStart;
     ParseNodeKind currentKind;
-#if DEBUG_HEADER
     VariantKind currentVariant;
-#endif // DEBUG_HEADER
-
+    mozilla::Vector<uint32_t> lengthsTable;
+    mozilla::Vector<std::string> stringsTable;
 
     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());
     }
 
@@ -664,104 +814,101 @@ private:
         in.read((char*)&char_kind, sizeof char_kind);
         if (char_kind >= mozilla::ArrayLength(NAMES)) {
             MOZ_CRASH("Bad kind");
         }
         currentKind = (ParseNodeKind)char_kind;
 
         if (currentKind == PNK_LIMIT) {
             currentByteLength = 0;
-#if DEBUG_HEADER
             currentVariant = VariantKind::IsLeaf;
-#endif // DEBUG_HEADER
         } else {
 #if DEBUG_HEADER
             // std::cerr << "handleHeader variantKind at " << in.tellg() << "\n";
             unsigned char char_variant;
             in.read((char*)&char_variant, sizeof char_variant);
             currentVariant = (VariantKind)char_variant;
             // std::cerr << "handleHeader reading variant: " << currentVariant << "\n";
             MOZ_ASSERT(currentVariant == VariantKind::IsLeaf || currentVariant == VariantKind::IsConcat);
 #endif // DEBUG_HEADER
-
-            // std::cerr << "handleHeader byteLength at " << in.tellg() << "\n";
-
-            currentByteLength = readVariableLength();
         }
-
-        currentByteStart = in.tellg();
     }
 
     void handleFooter() {
         // std::cerr << "handleFooter " << in.tellg() << "\n";
 
         // Check byteLength, footer
-        const size_t byteStop = in.tellg();
 
-        MOZ_ASSERT(byteStop - currentByteStart == currentByteLength);
+        if (currentVariant == VariantKind::IsConcat) {
+            const size_t byteStop = in.tellg();
+            MOZ_ASSERT(byteStop - currentByteStart == currentByteLength);
+        }
 
 #if DEBUG_HEADER
         std::string footer(sizeof FOOTER - 1, '\0');
         in.read(&footer[0], sizeof FOOTER - 1);
         if (footer.compare(0, sizeof FOOTER - 1, FOOTER) != 0) {
             MOZ_CRASH("Bad footer");
         }
 #endif // DEBUG_HEADER
 
         // std::cerr << "handleFooter done " << in.tellg() << "\n";
     }
 
     uint32_t readVariableLength() {
         uint32_t val = 0;
         uint32_t shift = 0;
         while (true) {
-            // std::cerr << "readVariableLength at " << in.tellg() << "\n";
             MOZ_ASSERT(shift < 32);
             int get = in.get();
             // FIXME: BIG HACK because istringstream doesn't like embedded \0.
             if (get < 0) {
                 get = 0;
                 in.clear();
             }
             const uint8_t byte = (uint8_t)get;
             val |= (uint32_t(byte) >> 1) << shift;
             shift += 7;
             if (!(byte & 1))
                 return val;
         }
     }
 
+    void getLeafAsString(uint32_t id, std::string& data)
+    {
+        data = stringsTable[id];
+    }
+
     MOZ_MUST_USE ParseNode* parseNode(ParseNodeKind kind)
     {
         debug << NAMES[kind] << " ";
         // std::cerr << "parseNode " << NAMES[kind] << "\n";
 
         switch (kind) {
             // Empty subtree.
             case PNK_LIMIT: {
-                std::string leaf;
                 return nullptr;
             }
 
             case PNK_LEXICALSCOPE: {
                 const size_t length = readNodeAsConcat();
                 MOZ_ASSERT(length == 1);
 
-                UniquePtr<ParseNode> body(parseNode());
+                UniquePtr<ParseNode> body(parseNodeAux());
                 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());
+                UniquePtr<ParseNode> body(parseNodeAux());
                 MOZ_ASSERT(body);
 
                 CodeNode* result(new_<CodeNode>(kind, JSOP_NOP, TokenPos()));
                 result->pn_body = body.release();
                 --functionDepth;
                 return result;
             }
 
@@ -771,19 +918,19 @@ private:
             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());
+                UniquePtr<ParseNode> kid1(parseNodeAux());
+                UniquePtr<ParseNode> kid2(parseNodeAux());
+                UniquePtr<ParseNode> kid3(parseNodeAux());
                 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;
@@ -812,18 +959,18 @@ private:
             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());
+                UniquePtr<ParseNode> left(parseNodeAux());
+                UniquePtr<ParseNode> right(parseNodeAux());
 
                 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());
                 }
@@ -857,17 +1004,17 @@ private:
             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());
+                return new_<UnaryNode>(kind, JSOP_NOP, TokenPos(), parseNodeAux());
             }
 
             // 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;
@@ -911,17 +1058,17 @@ private:
             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();
+                    ParseNode* current = parseNodeAux();
                     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;
@@ -930,17 +1077,17 @@ private:
 
                 return result.release();
             }
 
             // Labels
             case PNK_BREAK: MOZ_FALLTHROUGH;
             case PNK_CONTINUE: {
                 std::string leaf;
-                readNodeAsLeaf(leaf);
+                getLeafAsString(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());
@@ -949,49 +1096,49 @@ private:
 
             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);
+                getLeafAsString(parseLeaf(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());
+                UniquePtr<ParseNode> expr(parseNodeAux());
                 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);
+                getLeafAsString(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);
+                getLeafAsString(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);
 
@@ -1004,58 +1151,49 @@ private:
             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);
+                mozilla::Unused << readNodeAsLeaf();
 
                 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);
+                mozilla::Unused << readNodeAsLeaf();
 
                 return new_<NullaryNode>(kind, JSOP_NOP, TokenPos());
             }
 
             // Stuff we don't handle yet:
             case PNK_REGEXP: {
                 std::string leaf;
-                readNodeAsLeaf(leaf);
+                getLeafAsString(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;
+    TreeSerializer serializer(cx, debug);
+    return serializer.treeSerialize(node, out);
 }
 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
--- a/js/src/frontend/BinaryAST.h
+++ b/js/src/frontend/BinaryAST.h
@@ -12,17 +12,17 @@ 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(const std::string&& data);
         Leaf(); // Empty leaf
     };
     struct Concat {
         const mozilla::Vector<UniquePtr<StringTree>> children;
         Concat(mozilla::Vector<UniquePtr<StringTree>>&&);
     };
     enum VariantKind {
         IsLeaf = 128,
@@ -42,30 +42,27 @@ struct StringTree {
     const Variant data;
     const unsigned char kind; // Size-efficient representation of ParseNodeKind.
 
     // 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;
+    const std::string& 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