Bug 1275292 - Delete ParseNodeAllocator::freeTree() and friends. r=shu.
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 27 Feb 2018 15:09:09 -0600
changeset 471728 b512c7a263b259b844aefd723301fb7643e1bfd2
parent 471727 ca285aed3926966562f9444b912eb24d757d2657
child 471729 89bc67f5eaaf6348c102e2b0aa6152b87b9c99cc
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs1275292
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1275292 - Delete ParseNodeAllocator::freeTree() and friends. r=shu.
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/FoldConstants.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.h
js/src/frontend/SyntaxParseHandler.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -338,17 +338,16 @@ BytecodeCompiler::compileScript(HandleOb
                 // scope chain.
                 if (!deoptimizeArgumentsInEnclosingScripts(cx, environment))
                     return nullptr;
             }
             if (!emitter->emitScript(pn))
                 return nullptr;
             if (!NameFunctions(cx, pn))
                 return nullptr;
-            parser->handler.freeTree(pn);
 
             break;
         }
 
         // Maybe we aborted a syntax parse. See if we can try again.
         if (!handleParseFailure(directives, startPosition))
             return nullptr;
 
@@ -412,18 +411,16 @@ BytecodeCompiler::compileModule()
     if (!emplaceEmitter(emitter, &modulesc))
         return nullptr;
     if (!emitter->emitScript(pn->pn_body))
         return nullptr;
 
     if (!NameFunctions(cx, pn))
         return nullptr;
 
-    parser->handler.freeTree(pn);
-
     if (!builder.initModule())
         return nullptr;
 
     RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
     if (!env)
         return nullptr;
 
     module->setInitialEnvironment(env);
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -512,17 +512,16 @@ FoldCondition(JSContext* cx, ParseNode**
     ParseNode* node = *nodePtr;
     Truthiness t = Boolish(node);
     if (t != Unknown) {
         // We can turn function nodes into constant nodes here, but mutating
         // function nodes is tricky --- in particular, mutating a function node
         // that appears on a method list corrupts the method list. However,
         // methods are M's in statements of the form 'this.foo = M;', which we
         // never fold, so we're okay.
-        parser.prepareNodeForMutation(node);
         if (t == Truthy) {
             node->setKind(ParseNodeKind::True);
             node->setOp(JSOP_TRUE);
         } else {
             node->setKind(ParseNodeKind::False);
             node->setOp(JSOP_FALSE);
         }
         node->setArity(PN_NULLARY);
@@ -550,18 +549,16 @@ FoldTypeOfExpr(JSContext* cx, ParseNode*
     else if (expr->isKind(ParseNodeKind::Null))
         result = cx->names().object;
     else if (expr->isKind(ParseNodeKind::True) || expr->isKind(ParseNodeKind::False))
         result = cx->names().boolean;
     else if (expr->isKind(ParseNodeKind::Function))
         result = cx->names().function;
 
     if (result) {
-        parser.prepareNodeForMutation(node);
-
         node->setKind(ParseNodeKind::String);
         node->setArity(PN_NULLARY);
         node->setOp(JSOP_NOP);
         node->pn_atom = result;
     }
 
     return true;
 }
@@ -574,17 +571,16 @@ FoldDeleteExpr(JSContext* cx, ParseNode*
 
     ParseNode*& expr = node->pn_kid;
     if (!Fold(cx, &expr, parser))
         return false;
 
     // Expression deletion evaluates the expression, then evaluates to true.
     // For effectless expressions, eliminate the expression evaluation.
     if (IsEffectless(expr)) {
-        parser.prepareNodeForMutation(node);
         node->setKind(ParseNodeKind::True);
         node->setArity(PN_NULLARY);
         node->setOp(JSOP_TRUE);
     }
 
     return true;
 }
 
@@ -641,29 +637,27 @@ FoldNot(JSContext* cx, ParseNode* node, 
 
     ParseNode*& expr = node->pn_kid;
     if (!FoldCondition(cx, &expr, parser))
         return false;
 
     if (expr->isKind(ParseNodeKind::Number)) {
         double d = expr->pn_dval;
 
-        parser.prepareNodeForMutation(node);
         if (d == 0 || IsNaN(d)) {
             node->setKind(ParseNodeKind::True);
             node->setOp(JSOP_TRUE);
         } else {
             node->setKind(ParseNodeKind::False);
             node->setOp(JSOP_FALSE);
         }
         node->setArity(PN_NULLARY);
     } else if (expr->isKind(ParseNodeKind::True) || expr->isKind(ParseNodeKind::False)) {
         bool newval = !expr->isKind(ParseNodeKind::True);
 
-        parser.prepareNodeForMutation(node);
         node->setKind(newval ? ParseNodeKind::True : ParseNodeKind::False);
         node->setArity(PN_NULLARY);
         node->setOp(newval ? JSOP_TRUE : JSOP_FALSE);
     }
 
     return true;
 }
 
@@ -690,17 +684,16 @@ FoldUnaryArithmetic(JSContext* cx, Parse
 
         if (node->isKind(ParseNodeKind::BitNot))
             d = ~ToInt32(d);
         else if (node->isKind(ParseNodeKind::Neg))
             d = -d;
         else
             MOZ_ASSERT(node->isKind(ParseNodeKind::Pos)); // nothing to do
 
-        parser.prepareNodeForMutation(node);
         node->setKind(ParseNodeKind::Number);
         node->setOp(JSOP_DOUBLE);
         node->setArity(PN_NULLARY);
         node->pn_dval = d;
     }
 
     return true;
 }
@@ -749,40 +742,35 @@ FoldAndOr(JSContext* cx, ParseNode** nod
             continue;
         }
 
         // If the constant-folded node's truthiness will terminate the
         // condition -- `a || true || expr` or |b && false && expr| -- then
         // trailing nodes will never be evaluated.  Truncate the list after
         // the known-truthiness node, as it's the overall result.
         if ((t == Truthy) == isOrNode) {
-            ParseNode* afterNext;
-            for (ParseNode* next = (*elem)->pn_next; next; next = afterNext) {
-                afterNext = next->pn_next;
-                parser.freeTree(next);
+            for (ParseNode* next = (*elem)->pn_next; next; next = next->pn_next)
                 --node->pn_count;
-            }
 
             // Terminate the original and/or list at the known-truthiness
             // node.
             (*elem)->pn_next = nullptr;
             elem = &(*elem)->pn_next;
             break;
         }
 
         MOZ_ASSERT((t == Truthy) == !isOrNode);
 
-        // We've encountered a vacuous node that'll never short- circuit
+        // We've encountered a vacuous node that'll never short-circuit
         // evaluation.
         if ((*elem)->pn_next) {
             // This node is never the overall result when there are
             // subsequent nodes.  Remove it.
             ParseNode* elt = *elem;
             *elem = elt->pn_next;
-            parser.freeTree(elt);
             --node->pn_count;
         } else {
             // Otherwise this node is the result of the overall expression,
             // so leave it alone.  And we're done.
             elem = &(*elem)->pn_next;
             break;
         }
     } while (*elem);
@@ -793,20 +781,16 @@ FoldAndOr(JSContext* cx, ParseNode** nod
 
     node->checkListConsistency();
 
     // If we removed nodes, we may have to replace a one-element list with
     // its element.
     if (node->pn_count == 1) {
         ParseNode* first = node->pn_head;
         ReplaceNode(nodePtr, first);
-
-        node->setKind(ParseNodeKind::Null);
-        node->setArity(PN_NULLARY);
-        parser.freeTree(node);
     }
 
     return true;
 }
 
 static bool
 FoldConditional(JSContext* cx, ParseNode** nodePtr, PerHandlerParser<FullParseHandler>& parser)
 {
@@ -848,34 +832,24 @@ FoldConditional(JSContext* cx, ParseNode
         }
 
         // Try to constant-fold based on the condition expression.
         Truthiness t = Boolish(expr);
         if (t == Unknown)
             continue;
 
         // Otherwise reduce 'C ? T : F' to T or F as directed by C.
-        ParseNode* replacement;
-        ParseNode* discarded;
-        if (t == Truthy) {
-            replacement = ifTruthy;
-            discarded = ifFalsy;
-        } else {
-            replacement = ifFalsy;
-            discarded = ifTruthy;
-        }
+        ParseNode* replacement = t == Truthy ? ifTruthy : ifFalsy;
 
         // Otherwise perform a replacement.  This invalidates |nextNode|, so
         // reset it (if the replacement requires folding) or clear it (if
         // |ifFalsy| is dead code) as needed.
         if (nextNode)
             nextNode = (*nextNode == replacement) ? nodePtr : nullptr;
         ReplaceNode(nodePtr, replacement);
-
-        parser.freeTree(discarded);
     } while (nextNode);
 
     return true;
 }
 
 static bool
 FoldIf(JSContext* cx, ParseNode** nodePtr, PerHandlerParser<FullParseHandler>& parser)
 {
@@ -945,37 +919,26 @@ FoldIf(JSContext* cx, ParseNode** nodePt
 
         if (!performReplacement)
             continue;
 
         if (!replacement) {
             // If there's no replacement node, we have a constantly-false |if|
             // with no |else|.  Replace the entire thing with an empty
             // statement list.
-            parser.prepareNodeForMutation(node);
             node->setKind(ParseNodeKind::StatementList);
             node->setArity(PN_LIST);
             node->makeEmpty();
         } else {
             // Replacement invalidates |nextNode|, so reset it (if the
             // replacement requires folding) or clear it (if |alternative|
             // is dead code) as needed.
             if (nextNode)
                 nextNode = (*nextNode == replacement) ? nodePtr : nullptr;
             ReplaceNode(nodePtr, replacement);
-
-            // Morph the original node into a discardable node, then
-            // aggressively free it and the discarded arm (if any) to suss out
-            // any bugs in the preceding logic.
-            node->setKind(ParseNodeKind::StatementList);
-            node->setArity(PN_LIST);
-            node->makeEmpty();
-            if (discarded)
-                node->append(discarded);
-            parser.freeTree(node);
         }
     } while (nextNode);
 
     return true;
 }
 
 static bool
 FoldFunction(JSContext* cx, ParseNode* node, PerHandlerParser<FullParseHandler>& parser)
@@ -1072,19 +1035,17 @@ FoldBinaryArithmetic(JSContext* cx, Pars
     if (elem->isKind(ParseNodeKind::Number)) {
         ParseNodeKind kind = node->getKind();
         while (true) {
             if (!next || !next->isKind(ParseNodeKind::Number))
                 break;
 
             double d = ComputeBinary(kind, elem->pn_dval, next->pn_dval);
 
-            ParseNode* afterNext = next->pn_next;
-            parser.freeTree(next);
-            next = afterNext;
+            next = next->pn_next;
             elem->pn_next = next;
 
             elem->setKind(ParseNodeKind::Number);
             elem->setOp(JSOP_DOUBLE);
             elem->setArity(PN_NULLARY);
             elem->pn_dval = d;
 
             node->pn_count--;
@@ -1094,18 +1055,16 @@ FoldBinaryArithmetic(JSContext* cx, Pars
             MOZ_ASSERT(node->pn_head == elem);
             MOZ_ASSERT(elem->isKind(ParseNodeKind::Number));
 
             double d = elem->pn_dval;
             node->setKind(ParseNodeKind::Number);
             node->setArity(PN_NULLARY);
             node->setOp(JSOP_DOUBLE);
             node->pn_dval = d;
-
-            parser.freeTree(elem);
         }
     }
 
     return true;
 }
 
 static bool
 FoldExponentiation(JSContext* cx, ParseNode* node, PerHandlerParser<FullParseHandler>& parser)
@@ -1137,17 +1096,16 @@ FoldExponentiation(JSContext* cx, ParseN
 
     ParseNode* base = node->pn_head;
     ParseNode* exponent = base->pn_next;
     if (!base->isKind(ParseNodeKind::Number) || !exponent->isKind(ParseNodeKind::Number))
         return true;
 
     double d1 = base->pn_dval, d2 = exponent->pn_dval;
 
-    parser.prepareNodeForMutation(node);
     node->setKind(ParseNodeKind::Number);
     node->setArity(PN_NULLARY);
     node->setOp(JSOP_DOUBLE);
     node->pn_dval = ecmaPow(d1, d2);
     return true;
 }
 
 static bool
@@ -1297,26 +1255,16 @@ FoldElement(JSContext* cx, ParseNode** n
     // Optimization 3: We have expr["foo"] where foo is not an index.  Convert
     // to a property access (like expr.foo) that optimizes better downstream.
     ParseNode* dottedAccess = parser.newPropertyAccess(expr, name, node->pn_pos.end);
     if (!dottedAccess)
         return false;
     dottedAccess->setInParens(node->isInParens());
     ReplaceNode(nodePtr, dottedAccess);
 
-    // If we've replaced |expr["prop"]| with |expr.prop|, we can now free the
-    // |"prop"| and |expr["prop"]| nodes -- but not the |expr| node that we're
-    // now using as a sub-node of |dottedAccess|.  Munge |expr["prop"]| into a
-    // node with |"prop"| as its only child, that'll pass AST sanity-checking
-    // assertions during freeing, then free it.
-    node->setKind(ParseNodeKind::TypeOfExpr);
-    node->setArity(PN_UNARY);
-    node->pn_kid = key;
-    parser.freeTree(node);
-
     return true;
 }
 
 static bool
 FoldAdd(JSContext* cx, ParseNode** nodePtr, PerHandlerParser<FullParseHandler>& parser)
 {
     ParseNode* node = *nodePtr;
 
@@ -1338,17 +1286,16 @@ FoldAdd(JSContext* cx, ParseNode** nodeP
     ParseNode* next = current->pn_next;
     if (current->isKind(ParseNodeKind::Number)) {
         do {
             if (!next->isKind(ParseNodeKind::Number))
                 break;
 
             current->pn_dval += next->pn_dval;
             current->pn_next = next->pn_next;
-            parser.freeTree(next);
             next = current->pn_next;
 
             MOZ_ASSERT(node->pn_count > 1);
             node->pn_count--;
         } while (next);
     }
 
     // If any operands remain, attempt string concatenation folding.
@@ -1399,19 +1346,18 @@ FoldAdd(JSContext* cx, ParseNode** nodeP
                     break;
 
                 // Add this string to the combination and remove the node.
                 tmp = next->pn_atom;
                 combination = ConcatStrings<CanGC>(cx, combination, tmp);
                 if (!combination)
                     return false;
 
-                current->pn_next = next->pn_next;
-                parser.freeTree(next);
-                next = current->pn_next;
+                next = next->pn_next;
+                current->pn_next = next;
 
                 MOZ_ASSERT(node->pn_count > 1);
                 node->pn_count--;
             } while (next);
 
             // Replace |current|'s string with the entire combination.
             MOZ_ASSERT(current->isKind(ParseNodeKind::String));
             combination = AtomizeString(cx, combination);
@@ -1449,22 +1395,16 @@ FoldAdd(JSContext* cx, ParseNode** nodeP
 
     node->pn_tail = &current->pn_next;
     node->checkListConsistency();
 
     if (node->pn_count == 1) {
         // We reduced the list to a constant.  Replace the ParseNodeKind::Add node
         // with that constant.
         ReplaceNode(nodePtr, current);
-
-        // Free the old node to aggressively verify nothing uses it.
-        node->setKind(ParseNodeKind::True);
-        node->setArity(PN_NULLARY);
-        node->setOp(JSOP_TRUE);
-        parser.freeTree(node);
     }
 
     return true;
 }
 
 static bool
 FoldCall(JSContext* cx, ParseNode* node, PerHandlerParser<FullParseHandler>& parser)
 {
@@ -1522,20 +1462,18 @@ FoldForHead(JSContext* cx, ParseNode* no
         if (!Fold(cx, &init, parser))
             return false;
     }
 
     if (ParseNode*& test = node->pn_kid2) {
         if (!FoldCondition(cx, &test, parser))
             return false;
 
-        if (test->isKind(ParseNodeKind::True)) {
-            parser.freeTree(test);
+        if (test->isKind(ParseNodeKind::True))
             test = nullptr;
-        }
     }
 
     if (ParseNode*& update = node->pn_kid3) {
         if (!Fold(cx, &update, parser))
             return false;
     }
 
     return true;
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -92,19 +92,16 @@ class FullParseHandler
 
     // The FullParseHandler may be used to create nodes for text sources
     // (from Parser.h) or for binary sources (from BinSource.h). In the latter
     // case, some common assumptions on offsets are incorrect, e.g. in `a + b`,
     // `a`, `b` and `+` may be stored in any order. We use `sourceKind()`
     // to determine whether we need to check these assumptions.
     SourceKind sourceKind() const { return sourceKind_; }
 
-    ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
-    void prepareNodeForMutation(ParseNode* pn) { return allocator.prepareNodeForMutation(pn); }
-
     ParseNode* newName(PropertyName* name, const TokenPos& pos, JSContext* cx)
     {
         return new_<NameNode>(ParseNodeKind::Name, JSOP_GETNAME, name, pos);
     }
 
     ParseNode* newComputedName(ParseNode* expr, uint32_t begin, uint32_t end) {
         TokenPos pos(begin, end);
         return new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr);
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -37,520 +37,23 @@ ParseNode::checkListConsistency()
     } else {
         tail = &pn_head;
     }
     MOZ_ASSERT(pn_tail == tail);
     MOZ_ASSERT(pn_count == count);
 }
 #endif
 
-/* Add |node| to |parser|'s free node list. */
-void
-ParseNodeAllocator::freeNode(ParseNode* pn)
-{
-    /* Catch back-to-back dup recycles. */
-    MOZ_ASSERT(pn != freelist);
-
-#ifdef DEBUG
-    /* Poison the node, to catch attempts to use it without initializing it. */
-    memset(pn, 0xab, sizeof(*pn));
-#endif
-
-    pn->pn_next = freelist;
-    freelist = pn;
-}
-
-namespace {
-
-/*
- * A work pool of ParseNodes. The work pool is a stack, chained together
- * by nodes' pn_next fields. We use this to avoid creating deep C++ stacks
- * when recycling deep parse trees.
- *
- * Since parse nodes are probably allocated in something close to the order
- * they appear in a depth-first traversal of the tree, making the work pool
- * a stack should give us pretty good locality.
- */
-class NodeStack {
-  public:
-    NodeStack() : top(nullptr) { }
-    bool empty() { return top == nullptr; }
-    void push(ParseNode* pn) {
-        pn->pn_next = top;
-        top = pn;
-    }
-    /* Push the children of the PN_LIST node |pn| on the stack. */
-    void pushList(ParseNode* pn) {
-        /* This clobbers pn->pn_head if the list is empty; should be okay. */
-        *pn->pn_tail = top;
-        top = pn->pn_head;
-    }
-    ParseNode* pop() {
-        MOZ_ASSERT(!empty());
-        ParseNode* hold = top; /* my kingdom for a prog1 */
-        top = top->pn_next;
-        return hold;
-    }
-  private:
-    ParseNode* top;
-};
-
-} /* anonymous namespace */
-
-enum class PushResult { Recyclable, CleanUpLater };
-
-static PushResult
-PushCodeNodeChildren(ParseNode* node, NodeStack* stack)
-{
-    MOZ_ASSERT(node->isArity(PN_CODE));
-
-    /*
-     * Function nodes are linked into the function box tree, and may appear
-     * on method lists. Both of those lists are singly-linked, so trying to
-     * update them now could result in quadratic behavior when recycling
-     * trees containing many functions; and the lists can be very long. So
-     * we put off cleaning the lists up until just before function
-     * analysis, when we call CleanFunctionList.
-     *
-     * In fact, we can't recycle the parse node yet, either: it may appear
-     * on a method list, and reusing the node would corrupt that. Instead,
-     * we clear its pn_funbox pointer to mark it as deleted;
-     * CleanFunctionList recycles it as well.
-     *
-     * We do recycle the nodes around it, though, so we must clear pointers
-     * to them to avoid leaving dangling references where someone can find
-     * them.
-     */
-    node->pn_funbox = nullptr;
-    if (node->pn_body)
-        stack->push(node->pn_body);
-    node->pn_body = nullptr;
-
-    return PushResult::CleanUpLater;
-}
-
-static PushResult
-PushNameNodeChildren(ParseNode* node, NodeStack* stack)
-{
-    MOZ_ASSERT(node->isArity(PN_NAME));
-
-    if (node->pn_expr)
-        stack->push(node->pn_expr);
-    node->pn_expr = nullptr;
-    return PushResult::Recyclable;
-}
-
-static PushResult
-PushScopeNodeChildren(ParseNode* node, NodeStack* stack)
-{
-    MOZ_ASSERT(node->isArity(PN_SCOPE));
-
-    if (node->scopeBody())
-        stack->push(node->scopeBody());
-    node->setScopeBody(nullptr);
-    return PushResult::Recyclable;
-}
-
-static PushResult
-PushListNodeChildren(ParseNode* node, NodeStack* stack)
-{
-    MOZ_ASSERT(node->isArity(PN_LIST));
-    node->checkListConsistency();
-
-    stack->pushList(node);
-
-    return PushResult::Recyclable;
-}
-
-static PushResult
-PushUnaryNodeChild(ParseNode* node, NodeStack* stack)
-{
-    MOZ_ASSERT(node->isArity(PN_UNARY));
-
-    stack->push(node->pn_kid);
-
-    return PushResult::Recyclable;
-}
-
-/*
- * Push the children of |pn| on |stack|. Return true if |pn| itself could be
- * safely recycled, or false if it must be cleaned later (pn_used and pn_defn
- * nodes, and all function nodes; see comments for CleanFunctionList in
- * SemanticAnalysis.cpp). Some callers want to free |pn|; others
- * (js::ParseNodeAllocator::prepareNodeForMutation) don't care about |pn|, and
- * just need to take care of its children.
- */
-static PushResult
-PushNodeChildren(ParseNode* pn, NodeStack* stack)
-{
-    switch (pn->getKind()) {
-      // Trivial nodes that refer to no nodes, are referred to by nothing
-      // but their parents, are never used, and are never a definition.
-      case ParseNodeKind::EmptyStatement:
-      case ParseNodeKind::String:
-      case ParseNodeKind::TemplateString:
-      case ParseNodeKind::RegExp:
-      case ParseNodeKind::True:
-      case ParseNodeKind::False:
-      case ParseNodeKind::Null:
-      case ParseNodeKind::RawUndefined:
-      case ParseNodeKind::Elision:
-      case ParseNodeKind::Generator:
-      case ParseNodeKind::Number:
-      case ParseNodeKind::Break:
-      case ParseNodeKind::Continue:
-      case ParseNodeKind::Debugger:
-      case ParseNodeKind::ExportBatchSpec:
-      case ParseNodeKind::ObjectPropertyName:
-      case ParseNodeKind::PosHolder:
-        MOZ_ASSERT(pn->isArity(PN_NULLARY));
-        return PushResult::Recyclable;
-
-      // Nodes with a single non-null child.
-      case ParseNodeKind::ExpressionStatement:
-      case ParseNodeKind::TypeOfName:
-      case ParseNodeKind::TypeOfExpr:
-      case ParseNodeKind::Void:
-      case ParseNodeKind::Not:
-      case ParseNodeKind::BitNot:
-      case ParseNodeKind::Throw:
-      case ParseNodeKind::DeleteName:
-      case ParseNodeKind::DeleteProp:
-      case ParseNodeKind::DeleteElem:
-      case ParseNodeKind::DeleteExpr:
-      case ParseNodeKind::Pos:
-      case ParseNodeKind::Neg:
-      case ParseNodeKind::PreIncrement:
-      case ParseNodeKind::PostIncrement:
-      case ParseNodeKind::PreDecrement:
-      case ParseNodeKind::PostDecrement:
-      case ParseNodeKind::ComputedName:
-      case ParseNodeKind::Spread:
-      case ParseNodeKind::MutateProto:
-      case ParseNodeKind::Export:
-      case ParseNodeKind::SuperBase:
-        return PushUnaryNodeChild(pn, stack);
-
-      // Nodes with a single nullable child.
-      case ParseNodeKind::This: {
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        if (pn->pn_kid)
-            stack->push(pn->pn_kid);
-        return PushResult::Recyclable;
-      }
-
-      // Binary nodes with two non-null children.
-
-      // All assignment and compound assignment nodes qualify.
-      case ParseNodeKind::Assign:
-      case ParseNodeKind::AddAssign:
-      case ParseNodeKind::SubAssign:
-      case ParseNodeKind::BitOrAssign:
-      case ParseNodeKind::BitXorAssign:
-      case ParseNodeKind::BitAndAssign:
-      case ParseNodeKind::LshAssign:
-      case ParseNodeKind::RshAssign:
-      case ParseNodeKind::UrshAssign:
-      case ParseNodeKind::MulAssign:
-      case ParseNodeKind::DivAssign:
-      case ParseNodeKind::ModAssign:
-      case ParseNodeKind::PowAssign:
-      // ...and a few others.
-      case ParseNodeKind::Elem:
-      case ParseNodeKind::ImportSpec:
-      case ParseNodeKind::ExportSpec:
-      case ParseNodeKind::Colon:
-      case ParseNodeKind::Shorthand:
-      case ParseNodeKind::DoWhile:
-      case ParseNodeKind::While:
-      case ParseNodeKind::Switch:
-      case ParseNodeKind::ClassMethod:
-      case ParseNodeKind::NewTarget:
-      case ParseNodeKind::SetThis:
-      case ParseNodeKind::For:
-      case ParseNodeKind::With: {
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        stack->push(pn->pn_left);
-        stack->push(pn->pn_right);
-        return PushResult::Recyclable;
-      }
-
-      // Default clauses are ParseNodeKind::Case but do not have case
-      // expressions. Named class expressions do not have outer binding nodes.
-      // So both are binary nodes with a possibly-null pn_left.
-      case ParseNodeKind::Case:
-      case ParseNodeKind::ClassNames: {
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        if (pn->pn_left)
-            stack->push(pn->pn_left);
-        stack->push(pn->pn_right);
-        return PushResult::Recyclable;
-      }
-
-      // The child is an assignment of a ParseNodeKind::Generator node to the
-      // '.generator' local, for a synthesized, prepended initial yield.
-      case ParseNodeKind::InitialYield: {
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Assign) &&
-                   pn->pn_kid->pn_left->isKind(ParseNodeKind::Name) &&
-                   pn->pn_kid->pn_right->isKind(ParseNodeKind::Generator));
-        stack->push(pn->pn_kid);
-        return PushResult::Recyclable;
-      }
-
-      // The child is the expression being yielded.
-      case ParseNodeKind::YieldStar:
-      case ParseNodeKind::Yield:
-      case ParseNodeKind::Await: {
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        if (pn->pn_kid)
-            stack->push(pn->pn_kid);
-        return PushResult::Recyclable;
-      }
-
-      // A return node's child is what you'd expect: the return expression,
-      // if any.
-      case ParseNodeKind::Return: {
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        if (pn->pn_kid)
-            stack->push(pn->pn_kid);
-        return PushResult::Recyclable;
-      }
-
-      // Import and export-from nodes have a list of specifiers on the left
-      // and a module string on the right.
-      case ParseNodeKind::Import:
-      case ParseNodeKind::ExportFrom: {
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        MOZ_ASSERT_IF(pn->isKind(ParseNodeKind::Import),
-                      pn->pn_left->isKind(ParseNodeKind::ImportSpecList));
-        MOZ_ASSERT_IF(pn->isKind(ParseNodeKind::ExportFrom),
-                      pn->pn_left->isKind(ParseNodeKind::ExportSpecList));
-        MOZ_ASSERT(pn->pn_left->isArity(PN_LIST));
-        MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::String));
-        stack->pushList(pn->pn_left);
-        stack->push(pn->pn_right);
-        return PushResult::Recyclable;
-      }
-
-      case ParseNodeKind::ExportDefault: {
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        MOZ_ASSERT_IF(pn->pn_right, pn->pn_right->isKind(ParseNodeKind::Name));
-        stack->push(pn->pn_left);
-        if (pn->pn_right)
-            stack->push(pn->pn_right);
-        return PushResult::Recyclable;
-      }
-
-      // Ternary nodes with all children non-null.
-      case ParseNodeKind::Conditional: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        stack->push(pn->pn_kid1);
-        stack->push(pn->pn_kid2);
-        stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // For for-in and for-of, the first child is the left-hand side of the
-      // 'in' or 'of' (a declaration or an assignment target). The second
-      // child is always null, and the third child is the expression looped
-      // over.  For example, in |for (var p in obj)|, the first child is |var
-      // p|, the second child is null, and the third child is |obj|.
-      case ParseNodeKind::ForIn:
-      case ParseNodeKind::ForOf: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        MOZ_ASSERT(!pn->pn_kid2);
-        stack->push(pn->pn_kid1);
-        stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // for (;;) nodes have one child per optional component of the loop head.
-      case ParseNodeKind::ForHead: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        if (pn->pn_kid1)
-            stack->push(pn->pn_kid1);
-        if (pn->pn_kid2)
-            stack->push(pn->pn_kid2);
-        if (pn->pn_kid3)
-            stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // classes might have an optional node for the heritage, as well as the names
-      case ParseNodeKind::Class: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        if (pn->pn_kid1)
-            stack->push(pn->pn_kid1);
-        if (pn->pn_kid2)
-            stack->push(pn->pn_kid2);
-        stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // if-statement nodes have condition and consequent children and a
-      // possibly-null alternative.
-      case ParseNodeKind::If: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        stack->push(pn->pn_kid1);
-        stack->push(pn->pn_kid2);
-        if (pn->pn_kid3)
-            stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // try-statements have statements to execute, and one or both of a
-      // catch-list and a finally-block.
-      case ParseNodeKind::Try: {
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        MOZ_ASSERT(pn->pn_kid2 || pn->pn_kid3);
-        stack->push(pn->pn_kid1);
-        if (pn->pn_kid2)
-            stack->push(pn->pn_kid2);
-        if (pn->pn_kid3)
-            stack->push(pn->pn_kid3);
-        return PushResult::Recyclable;
-      }
-
-      // A catch node has left node as catch-variable pattern (or null if
-      // omitted) and right node as the statements in the catch block.
-      case ParseNodeKind::Catch: {
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        if (pn->pn_left)
-            stack->push(pn->pn_left);
-        stack->push(pn->pn_right);
-        return PushResult::Recyclable;
-      }
-
-      // List nodes with all non-null children.
-      case ParseNodeKind::Or:
-      case ParseNodeKind::And:
-      case ParseNodeKind::BitOr:
-      case ParseNodeKind::BitXor:
-      case ParseNodeKind::BitAnd:
-      case ParseNodeKind::StrictEq:
-      case ParseNodeKind::Eq:
-      case ParseNodeKind::StrictNe:
-      case ParseNodeKind::Ne:
-      case ParseNodeKind::Lt:
-      case ParseNodeKind::Le:
-      case ParseNodeKind::Gt:
-      case ParseNodeKind::Ge:
-      case ParseNodeKind::InstanceOf:
-      case ParseNodeKind::In:
-      case ParseNodeKind::Lsh:
-      case ParseNodeKind::Rsh:
-      case ParseNodeKind::Ursh:
-      case ParseNodeKind::Add:
-      case ParseNodeKind::Sub:
-      case ParseNodeKind::Star:
-      case ParseNodeKind::Div:
-      case ParseNodeKind::Mod:
-      case ParseNodeKind::Pow:
-      case ParseNodeKind::Pipeline:
-      case ParseNodeKind::Comma:
-      case ParseNodeKind::New:
-      case ParseNodeKind::Call:
-      case ParseNodeKind::SuperCall:
-      case ParseNodeKind::Array:
-      case ParseNodeKind::Object:
-      case ParseNodeKind::TemplateStringList:
-      case ParseNodeKind::TaggedTemplate:
-      case ParseNodeKind::CallSiteObj:
-      case ParseNodeKind::Var:
-      case ParseNodeKind::Const:
-      case ParseNodeKind::Let:
-      case ParseNodeKind::StatementList:
-      case ParseNodeKind::ImportSpecList:
-      case ParseNodeKind::ExportSpecList:
-      case ParseNodeKind::ParamsBody:
-      case ParseNodeKind::ClassMethodList:
-        return PushListNodeChildren(pn, stack);
-
-      case ParseNodeKind::Label:
-      case ParseNodeKind::Dot:
-      case ParseNodeKind::Name:
-        return PushNameNodeChildren(pn, stack);
-
-      case ParseNodeKind::LexicalScope:
-        return PushScopeNodeChildren(pn, stack);
-
-      case ParseNodeKind::Function:
-      case ParseNodeKind::Module:
-        return PushCodeNodeChildren(pn, stack);
-
-      case ParseNodeKind::Limit: // invalid sentinel value
-        MOZ_CRASH("invalid node kind");
-    }
-
-    MOZ_CRASH("bad ParseNodeKind");
-    return PushResult::CleanUpLater;
-}
-
-/*
- * Prepare |pn| to be mutated in place into a new kind of node. Recycle all
- * |pn|'s recyclable children (but not |pn| itself!), and disconnect it from
- * metadata structures (the function box tree).
- */
-void
-ParseNodeAllocator::prepareNodeForMutation(ParseNode* pn)
-{
-    // Nothing to do for nullary nodes.
-    if (pn->isArity(PN_NULLARY))
-        return;
-
-    // Put |pn|'s children (but not |pn| itself) on a work stack.
-    NodeStack stack;
-    PushNodeChildren(pn, &stack);
-
-    // For each node on the work stack, push its children on the work stack,
-    // and free the node if we can.
-    while (!stack.empty()) {
-        pn = stack.pop();
-        if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
-            freeNode(pn);
-    }
-}
-
-/*
- * Return the nodes in the subtree |pn| to the parser's free node list, for
- * reallocation.
- */
-ParseNode*
-ParseNodeAllocator::freeTree(ParseNode* pn)
-{
-    if (!pn)
-        return nullptr;
-
-    ParseNode* savedNext = pn->pn_next;
-
-    NodeStack stack;
-    for (;;) {
-        if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
-            freeNode(pn);
-        if (stack.empty())
-            break;
-        pn = stack.pop();
-    }
-
-    return savedNext;
-}
-
 /*
  * Allocate a ParseNode from parser's node freelist or, failing that, from
  * cx's temporary arena.
  */
 void*
 ParseNodeAllocator::allocNode()
 {
-    if (ParseNode* pn = freelist) {
-        freelist = pn->pn_next;
-        return pn;
-    }
-
     LifoAlloc::AutoFallibleScope fallibleAllocator(&alloc);
     void* p = alloc.alloc(sizeof (ParseNode));
     if (!p)
         ReportOutOfMemory(cx);
     return p;
 }
 
 ParseNode*
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1349,28 +1349,24 @@ struct ClassNode : public TernaryNode {
 #ifdef DEBUG
 void DumpParseTree(ParseNode* pn, GenericPrinter& out, int indent = 0);
 #endif
 
 class ParseNodeAllocator
 {
   public:
     explicit ParseNodeAllocator(JSContext* cx, LifoAlloc& alloc)
-      : cx(cx), alloc(alloc), freelist(nullptr)
+      : cx(cx), alloc(alloc)
     {}
 
     void* allocNode();
-    void freeNode(ParseNode* pn);
-    ParseNode* freeTree(ParseNode* pn);
-    void prepareNodeForMutation(ParseNode* pn);
 
   private:
     JSContext* cx;
     LifoAlloc& alloc;
-    ParseNode* freelist;
 };
 
 inline bool
 ParseNode::isConstant()
 {
     switch (pn_type) {
       case ParseNodeKind::Number:
       case ParseNodeKind::String:
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -531,19 +531,16 @@ class PerHandlerParser
 
     // If ParseHandler is SyntaxParseHandler:
     //   Clear whether the last syntax parse was aborted.
     // If ParseHandler is FullParseHandler:
     //   Do nothing.
     inline void clearAbortedSyntaxParse();
 
   public:
-    void prepareNodeForMutation(Node node) { handler.prepareNodeForMutation(node); }
-    void freeTree(Node node) { handler.freeTree(node); }
-
     bool isValidSimpleAssignmentTarget(Node node,
                                        FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
 
     Node newPropertyAccess(Node expr, PropertyName* key, uint32_t end) {
         return handler.newPropertyAccess(expr, key, end);
     }
 
     FunctionBox* newFunctionBox(Node fn, JSFunction* fun, uint32_t toStringStart,
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -163,19 +163,16 @@ class SyntaxParseHandler
 
   public:
     SyntaxParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction)
       : lastAtom(nullptr)
     {}
 
     static Node null() { return NodeFailure; }
 
-    void prepareNodeForMutation(Node node) {}
-    void freeTree(Node node) {}
-
     Node newName(PropertyName* name, const TokenPos& pos, JSContext* cx) {
         lastAtom = name;
         if (name == cx->names().arguments)
             return NodeArgumentsName;
         if (pos.begin + strlen("async") == pos.end && name == cx->names().async)
             return NodePotentialAsyncKeyword;
         if (name == cx->names().eval)
             return NodeEvalName;