Bug 1130811 - Refactor node recycling code into arity-specific methods. r=shu

--HG--
extra : rebase_source : aaaea8420fa4ad367c33fb11622aa4fff7886a91
This commit is contained in:
Jeff Walden 2015-02-10 01:00:01 -08:00
Родитель f33a2676da
Коммит 97dd474ebb
1 изменённых файлов: 149 добавлений и 72 удалений

Просмотреть файл

@ -98,6 +98,122 @@ class NodeStack {
} /* anonymous namespace */ } /* 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;
stack->pushUnlessNull(node->pn_body);
node->pn_body = nullptr;
return PushResult::CleanUpLater;
}
static PushResult
PushNameNodeChildren(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_NAME));
/*
* Because used/defn nodes appear in AtomDefnMaps and elsewhere, we
* don't recycle them. (We'll recover their storage when we free the
* temporary arena.) However, we do recycle the nodes around them, so
* clean up the pointers to avoid dangling references. The top-level
* decls table carries references to them that later iterations through
* the compileScript loop may find, so they need to be neat.
*
* pn_expr and pn_lexdef share storage; the latter isn't an owning
* reference.
*/
if (!node->isUsed()) {
stack->pushUnlessNull(node->pn_expr);
node->pn_expr = nullptr;
}
if (!node->isUsed() && !node->isDefn())
return PushResult::Recyclable;
return PushResult::CleanUpLater;
}
static PushResult
PushListNodeChildren(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_LIST));
node->checkListConsistency();
stack->pushList(node);
return PushResult::Recyclable;
}
static PushResult
PushTernaryNodeChildren(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_TERNARY));
stack->pushUnlessNull(node->pn_kid1);
stack->pushUnlessNull(node->pn_kid2);
stack->pushUnlessNull(node->pn_kid3);
return PushResult::Recyclable;
}
static PushResult
PushUnaryNodeChild(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_UNARY));
stack->pushUnlessNull(node->pn_kid);
return PushResult::Recyclable;
}
static PushResult
PushBinaryNodeChildren(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_BINARY) || node->isArity(PN_BINARY_OBJ));
// This is *probably* PNK_SHORTHAND, but that's not yet clear.
if (node->pn_left != node->pn_right)
stack->pushUnlessNull(node->pn_left);
stack->pushUnlessNull(node->pn_right);
return PushResult::Recyclable;
}
static PushResult
CanRecycleNullaryNode(ParseNode *node, NodeStack *stack)
{
MOZ_ASSERT(node->isArity(PN_NULLARY));
if (node->isUsed() || node->isDefn())
return PushResult::CleanUpLater;
return PushResult::Recyclable;
}
/* /*
* Push the children of |pn| on |stack|. Return true if |pn| itself could be * 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 * safely recycled, or false if it must be cleaned later (pn_used and pn_defn
@ -106,76 +222,36 @@ class NodeStack {
* (js::ParseNodeAllocator::prepareNodeForMutation) don't care about |pn|, and * (js::ParseNodeAllocator::prepareNodeForMutation) don't care about |pn|, and
* just need to take care of its children. * just need to take care of its children.
*/ */
static bool static PushResult
PushNodeChildren(ParseNode *pn, NodeStack *stack) PushNodeChildren(ParseNode *pn, NodeStack *stack)
{ {
switch (pn->getArity()) { switch (pn->getArity()) {
case PN_CODE: case PN_CODE:
/* return PushCodeNodeChildren(pn, stack);
* 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.
*/
pn->pn_funbox = nullptr;
stack->pushUnlessNull(pn->pn_body);
pn->pn_body = nullptr;
return false;
case PN_NAME: case PN_NAME:
/* return PushNameNodeChildren(pn, stack);
* Because used/defn nodes appear in AtomDefnMaps and elsewhere, we
* don't recycle them. (We'll recover their storage when we free the
* temporary arena.) However, we do recycle the nodes around them, so
* clean up the pointers to avoid dangling references. The top-level
* decls table carries references to them that later iterations through
* the compileScript loop may find, so they need to be neat.
*
* pn_expr and pn_lexdef share storage; the latter isn't an owning
* reference.
*/
if (!pn->isUsed()) {
stack->pushUnlessNull(pn->pn_expr);
pn->pn_expr = nullptr;
}
return !pn->isUsed() && !pn->isDefn();
case PN_LIST: case PN_LIST:
pn->checkListConsistency(); return PushListNodeChildren(pn, stack);
stack->pushList(pn);
break;
case PN_TERNARY: case PN_TERNARY:
stack->pushUnlessNull(pn->pn_kid1); return PushTernaryNodeChildren(pn, stack);
stack->pushUnlessNull(pn->pn_kid2);
stack->pushUnlessNull(pn->pn_kid3);
break;
case PN_BINARY: case PN_BINARY:
case PN_BINARY_OBJ: case PN_BINARY_OBJ:
if (pn->pn_left != pn->pn_right) return PushBinaryNodeChildren(pn, stack);
stack->pushUnlessNull(pn->pn_left);
stack->pushUnlessNull(pn->pn_right);
break;
case PN_UNARY:
stack->pushUnlessNull(pn->pn_kid);
break;
case PN_NULLARY:
return !pn->isUsed() && !pn->isDefn();
default:
;
}
return true; case PN_UNARY:
return PushUnaryNodeChild(pn, stack);
case PN_NULLARY:
return CanRecycleNullaryNode(pn, stack);
default:
MOZ_CRASH("huh?");
return PushResult::CleanUpLater;
}
} }
/* /*
@ -186,19 +262,20 @@ PushNodeChildren(ParseNode *pn, NodeStack *stack)
void void
ParseNodeAllocator::prepareNodeForMutation(ParseNode *pn) ParseNodeAllocator::prepareNodeForMutation(ParseNode *pn)
{ {
if (!pn->isArity(PN_NULLARY)) { // Nothing to do for nullary nodes.
/* Put |pn|'s children (but not |pn| itself) on a work stack. */ if (pn->isArity(PN_NULLARY))
NodeStack stack; return;
PushNodeChildren(pn, &stack);
/* // Put |pn|'s children (but not |pn| itself) on a work stack.
* For each node on the work stack, push its children on the work stack, NodeStack stack;
* and free the node if we can. PushNodeChildren(pn, &stack);
*/
while (!stack.empty()) { // For each node on the work stack, push its children on the work stack,
pn = stack.pop(); // and free the node if we can.
if (PushNodeChildren(pn, &stack)) while (!stack.empty()) {
freeNode(pn); pn = stack.pop();
} if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
freeNode(pn);
} }
} }
@ -216,7 +293,7 @@ ParseNodeAllocator::freeTree(ParseNode *pn)
NodeStack stack; NodeStack stack;
for (;;) { for (;;) {
if (PushNodeChildren(pn, &stack)) if (PushNodeChildren(pn, &stack) == PushResult::Recyclable)
freeNode(pn); freeNode(pn);
if (stack.empty()) if (stack.empty())
break; break;