/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "Fundamentals.h" #include "Pool.h" #include "ControlNodes.h" #include "ControlNodeScheduler.h" #include "FastBitSet.h" // ---------------------------------------------------------------------------- // LoopHierarchySet // // Create a new set of LoopHierachyNodes. Allocate the LoopHierarchyNode array of // length nLoops and the indexes array of length nNodes in the given pool. // LoopHierarchySet::LoopHierarchySet(Pool& pool, LoopHierarchyNode* nodesArray, Uint32 nNodes) : nodes(nodesArray) { indexes = new(pool) Int32[nNodes]; nextFree = 0; fill(indexes, &indexes[nNodes], -1); } // // Return the set's element corresponding to the ControlNode dfsNum equals nodeIndex. // LoopHierarchyNode& LoopHierarchySet::operator [] (const Uint32 nodeIndex) { Int32 index = indexes[nodeIndex]; // If index is -1 then this node is not part of the set yet. if (index == -1) { index = nextFree++; indexes[nodeIndex] = index; } return nodes[index]; } // ---------------------------------------------------------------------------- // ControlNodeScheduler // // Return true if node is ready. We will call a node N ready if each of its // incoming edges E: A->N are long. // inline bool nodeIsReady(ControlNode& node) { const DoublyLinkedList& predecessors = node.getPredecessors(); for (DoublyLinkedList::iterator i = predecessors.begin(); !predecessors.done(i); i = predecessors.advance(i)) if (!predecessors.get(i).longFlag) return false; return true; } // // Return true if node is ready with respect to 'respect'. We will call a node N // ready with respect to node P if, for each of N's incoming edges E: A->N, either // E is long or A equals P. // inline bool nodeIsReadyWithRespectTo(ControlNode& node, ControlNode& respect) { const DoublyLinkedList& predecessors = node.getPredecessors(); for (DoublyLinkedList::iterator i = predecessors.begin(); !predecessors.done(i); i = predecessors.advance(i)) { ControlEdge& edge = predecessors.get(i); if (!edge.longFlag && (&edge.getSource() != &respect)) return false; } return true; } // // Schedule the working nodes in loopToSchedule. // void ControlNodeScheduler::scheduleLoop(LoopHierarchyNode& loopToSchedule) { #if defined(DEBUG_SCHEDULER) fprintf(stdout, "ControlNodeScheduler: Will schedule loop headed by N%d\n", loopToSchedule.header); #endif // // Determine the nodes in a region. // FastBitSet& REG = loopToSchedule.nodes; // REG is the region of nodes contained in this loop. FastBitSet Rlocal(nNodes); // Rlocal is the set of nodes in REG that are not contained in any subloop of REG. FastBitSet Rsubheaders(nNodes); // Rsubheaders is the set of headers of immediate subloops of REG. FastBitSet Rcombined(nNodes); // Rcombined is the union of Rlocal and Rsubheaders. FastBitSet R(nNodes); // R is the intersection of Rcombined and the set of all working nodes. FastBitSet RR(nNodes); // RR is the intersection of REG and the set of all working nodes. Rlocal = REG; DoublyLinkedList& subLoops = loopToSchedule.getSuccessors(); for (DoublyLinkedList::iterator i = subLoops.begin(); !subLoops.done(i); i = subLoops.advance(i)) { LoopHierarchyNode& subLoop = subLoops.get(i); Rsubheaders.set(subLoop.header); Rlocal -= subLoop.nodes; } Rcombined = Rlocal; Rcombined |= Rsubheaders; R = Rcombined; RR = REG; FastBitSet nonWorkingNodes(nNodes); for (Int32 j = REG.firstOne(); j != -1; j = REG.nextOne(j)) if (!dfsList[j]->workingNode) nonWorkingNodes.set(j); R -= nonWorkingNodes; RR -= nonWorkingNodes; // // Initialize long edge flag for each edge E: A->B for which at least one of A or B is in R // Uint32 generation = ControlNode::getNextGeneration(); for (Int32 j = R.firstOne(); j != -1; j = R.nextOne(j)) { ControlNode& node = *dfsList[j]; ControlEdge* limit = node.getSuccessorsEnd(); for (ControlEdge* edge = node.getSuccessorsBegin(); edge != limit; edge++) if (edge->getTarget().generation != generation) initializeLongEdgeFlag(*edge, loopToSchedule, RR, Rlocal); const DoublyLinkedList& predecessors = node.getPredecessors(); for (DoublyLinkedList::iterator p = predecessors.begin(); !predecessors.done(p); p = predecessors.advance(p)) { ControlEdge& edge = predecessors.get(p); if (edge.getSource().generation != generation) initializeLongEdgeFlag(edge, loopToSchedule, RR, Rlocal); } node.generation = generation; } // // Linear scheduling. // FastBitSet W(nNodes); // W is the set of nodes in the region remaining to be scheduled. W = R; // Initially W contains all nodes in R except the loop header. W.clear(loopToSchedule.header); // Clique** cliqueStack = new(pool) Clique*[nNodes]; // Stack of Cliques waiting to be scheduled. Clique** cliqueStackPointer = cliqueStack; // Stack pointer. Int32 P = loopToSchedule.header; // P is the last node scheduled. It is initially the loop header. Int32 N; // Next node to schedule. Clique* currentClique = new(pool) Clique(); currentClique->addLast(scheduledNodes[P]); // The loop header is always the first node of the first clique. nodesAlreadyScheduled.set(P); loopToSchedule.cliques.addLast(*currentClique); while (!W.empty() || (cliqueStackPointer != cliqueStack)) { // Alternative 1. // If there exists a node N in W such that there exists at least one short edge from P to N // and node N is ready with respect to P, then we pick any such node N to be the next node. ControlNode& nodeP = *dfsList[P]; ControlEdge* limit = nodeP.getSuccessorsEnd(); for (ControlEdge* edge = nodeP.getSuccessorsBegin(); edge != limit; edge++) { ControlNode& target = edge->getTarget(); if (!edge->longFlag && nodeIsReadyWithRespectTo(target, nodeP) && W.test(target.dfsNum)) { N = target.dfsNum; goto foundNextNode; } } // Alternative 2. // Otherwise, if the stack isn't empty, we pop a clique Ck from the stack, make it become the next clique of // this region, make every edge from P to any node A remaining in W into a long edge, set P to be the // last node of Ck, and continue with alternative 1 above. if (cliqueStackPointer != cliqueStack) { Clique* nextClique = *--cliqueStackPointer; loopToSchedule.cliques.addLast(*nextClique); for (ControlEdge* edge = nodeP.getSuccessorsBegin(); edge != limit; edge++) if (W.test(edge->getTarget().dfsNum)) edge->longFlag = true; P = nextClique->last().dfsNum; currentClique = nextClique; continue; } // Alternative 3. // Otherwise, there must exist a node N in W such that node N is ready with respect to P. // We pick any such node N to be the next node. for (N = W.firstOne(); N != -1; N = W.nextOne(N)) if (nodeIsReadyWithRespectTo(*dfsList[N], *dfsList[P])) goto foundNextNode; PR_ASSERT(false); // Shouldn't happen. foundNextNode: // After we have picked node N, we remove N from the set W and make every edge // from P to any node A remaining in W into a long edge. W.clear(N); for (ControlEdge* e = nodeP.getSuccessorsBegin(); e != limit; e++) if (W.test(e->getTarget().dfsNum)) e->longFlag = true; // If N is not the header of a subloop. if (!Rsubheaders.test(N)) { ControlNode& nodeN = *dfsList[N]; bool nodePhasEdgeToNodeN = false; for (ControlEdge* edge = nodeP.getSuccessorsBegin(); edge != limit; edge++) { if (&edge->getTarget() == &nodeN) { // If there exists some edge from P to N then we append node N to the current // clique in our region's schedule nodePhasEdgeToNodeN = true; currentClique->addLast(scheduledNodes[N]); nodesAlreadyScheduled.set(N); break; } } if (!nodePhasEdgeToNodeN) { // There is node edge from P to N. We start a new clique in this region // and make N this clique's first node. Clique* newClique = new(pool) Clique(); newClique->addLast(scheduledNodes[N]); loopToSchedule.cliques.addLast(*newClique); nodesAlreadyScheduled.set(N); currentClique = newClique; } P = N; } else { // N is the header of a subloop. LoopHierarchyNode* subLoop = NULL; // Find the clique of the subloop that contains node N. DoublyLinkedList& subLoops = loopToSchedule.getSuccessors(); for (DoublyLinkedList::iterator i = subLoops.begin(); !subLoops.done(i); i = subLoops.advance(i)) { subLoop = &subLoops.get(i); if (subLoop->header == N) break; } PR_ASSERT(subLoop); Clique* subLoopClique = NULL; for (DoublyLinkedList::iterator c = subLoop->cliques.begin(); !subLoop->cliques.done(c); c = subLoop->cliques.advance(c)) { bool foundClique = false; subLoopClique = &subLoop->cliques.get(c); for (DoublyLinkedList::iterator n = subLoopClique->begin(); !subLoopClique->done(n); n = subLoopClique->advance(n)) if (subLoopClique->get(n).dfsNum == N) { foundClique = true; break; } if (foundClique) break; } PR_ASSERT(subLoopClique); ControlNode& nodeN = *dfsList[N]; bool nodePhasEdgeToNodeN = false; // If N is the first node of the subloop's clique and there exists some edge from P to N then we append // the entire clique to the current clique. Otherwise, we start a new clique and make the subloop's // clique become this new clique's beginning. for (ControlEdge* edge = nodeP.getSuccessorsBegin(); edge != limit; edge++) if (&edge->getTarget() == &nodeN) { nodePhasEdgeToNodeN = true; break; } if (!((subLoopClique->first().dfsNum == N) && nodePhasEdgeToNodeN)) { currentClique = new(pool) Clique(); loopToSchedule.cliques.addLast(*currentClique); } for (DoublyLinkedList::iterator s = subLoopClique->begin(); !subLoopClique->done(s);) { ScheduledNode& node = subLoopClique->get(s); s = subLoopClique->advance(s); node.remove(); currentClique->addLast(node); } subLoopClique->remove(); // If the subloop contained more than one clique, we push all remaining cliques onto the stack. // We are carefull to keep the scheduling order. for (DoublyLinkedList::iterator t = subLoop->cliques.end(); !subLoop->cliques.done(t);) { Clique& slc = subLoop->cliques.get(t); t = subLoop->cliques.retreat(t); slc.remove(); *cliqueStackPointer++ = &slc; } // The last node scheduled is the last node of the current clique. P = currentClique->last().dfsNum; } } // // Loop scheduling // // Find the last clique in this region such that the last node N of this clique satisfies the following properties: // 1.There exists an edge from N to H. // 2.There are no short edges E: N->A for which A is outside R. // 3.N is not a switch node. // 4.N is not H. for (DoublyLinkedList::iterator c = loopToSchedule.cliques.end(); !loopToSchedule.cliques.done(c); c = loopToSchedule.cliques.retreat(c)) { Clique& clique = loopToSchedule.cliques.get(c); ControlNode& lastNode = *dfsList[clique.last().dfsNum]; bool hasEdgeToHeader = false; bool noShortEdgeOutsiteR = true; ControlEdge* limit = lastNode.getSuccessorsEnd(); for (ControlEdge* edge = lastNode.getSuccessorsBegin(); edge != limit; edge++) { if (edge->getTarget().dfsNum == loopToSchedule.header) hasEdgeToHeader = true; if (!edge->longFlag && R.test(edge->getTarget().dfsNum)) noShortEdgeOutsiteR = false; } if (hasEdgeToHeader && noShortEdgeOutsiteR && (!lastNode.hasControlKind(ckSwitch)) && (lastNode.dfsNum != loopToSchedule.header)) { // We found this clique. We remove the last node N of this clique and prepend it to this // subloop's first clique. Now as long as this clique ends with some node N' that satisfies // all of the properties below, we remove N' from the end of this clique and prepend it // to this subloop's first clique. // // 1.There are no short edges E: N'->A for which A is outside R. // 2.N' is not a switch node. // 3.N' is not the loop's header. ScheduledNode& sNode = scheduledNodes[lastNode.dfsNum]; sNode.remove(); loopToSchedule.cliques.first().addFirst(sNode); for (DoublyLinkedList::iterator n = clique.end(); !clique.done(n);) { ControlNode& node = *dfsList[clique.get(n).dfsNum]; n = clique.retreat(n); bool noShortEdgeOutsiteR = true; ControlEdge* limit = node.getSuccessorsEnd(); for (ControlEdge* edge = node.getSuccessorsBegin(); edge != limit; edge++) if (!edge->longFlag && R.test(edge->getTarget().dfsNum)) { noShortEdgeOutsiteR = false; break; } if (noShortEdgeOutsiteR && (!node.hasControlKind(ckSwitch)) && (node.dfsNum != loopToSchedule.header)) { // The condition is satisfied. ScheduledNode& sNode = scheduledNodes[lastNode.dfsNum]; sNode.remove(); loopToSchedule.cliques.first().addFirst(sNode); } else break; } if (clique.empty()) clique.remove(); break; } } #if defined(DEBUG_SCHEDULER) fprintf(stdout, "done scheduled nodes: [ "); for (DoublyLinkedList::iterator x = loopToSchedule.cliques.begin(); !loopToSchedule.cliques.done(x); x = loopToSchedule.cliques.advance(x)) { fprintf(stdout, "[ "); Clique& clique = loopToSchedule.cliques.get(x); for (DoublyLinkedList::iterator z = clique.begin(); !clique.done(z); z = clique.advance(z)) fprintf(stdout, "N%d ", clique.get(z).dfsNum); fprintf(stdout, "] "); } fprintf(stdout, "]\n"); #endif } // // Set the long edge flag to the given edge in region. // void ControlNodeScheduler::initializeLongEdgeFlag(ControlEdge& edge, LoopHierarchyNode& inLoop, FastBitSet& RR, FastBitSet& Rlocal) { Int32 A = edge.getSource().dfsNum; if (!RR.test(A)) { // Condition 1 - A is outside RR. edge.longFlag = true; return; } Int32 B = edge.getTarget().dfsNum; if (A >= B) { // Condition 3 - edge is a backward edge. edge.longFlag = true; return; } if (edge.getSource().hasControlKind(ckSwitch)) { // Condition 4 - A is a switch node. edge.longFlag = true; return; } if (!Rlocal.test(A)) { // Condition 5 - A is in a subloop. DoublyLinkedList& subLoops = inLoop.getSuccessors(); for (DoublyLinkedList::iterator l = subLoops.begin(); !subLoops.done(l); l = subLoops.advance(l)) { LoopHierarchyNode& subLoop = subLoops.get(l); if (subLoop.nodes.test(A)) { // subLoop is the largest inLoop's subloop that contains A. for (DoublyLinkedList::iterator c = subLoop.cliques.begin(); !subLoop.cliques.done(c); c = subLoop.cliques.advance(c)) if (subLoop.cliques.get(c).last().dfsNum == A) { // A is the last node of this clique. edge.longFlag = false; return; } // If we get there then A is not the last node of some cliques edge.longFlag = true; return; } } } if (!Rlocal.test(B)) { // Condition 6 - B is in a subloop. DoublyLinkedList& subLoops = inLoop.getSuccessors(); for (DoublyLinkedList::iterator l = subLoops.begin(); !subLoops.done(l); l = subLoops.advance(l)) { LoopHierarchyNode& subLoop = subLoops.get(l); if (subLoop.nodes.test(B)) { // subLoop is the largest inLoop's subloop that contains B. for (DoublyLinkedList::iterator c = subLoop.cliques.begin(); !subLoop.cliques.done(c); c = subLoop.cliques.advance(c)) if (subLoop.cliques.get(c).last().dfsNum == B) { // B is the last node of this clique. edge.longFlag = false; return; } // If we get there then B is not the last node of some cliques edge.longFlag = true; return; } } } // Condition 2 - edge is any exception or return edge. switch (edge.getSource().getControlKind()) { case ckCatch: case ckEnd: edge.longFlag = true; return; default: break; } switch (edge.getTarget().getControlKind()) { case ckCatch: case ckEnd: edge.longFlag = true; return; default: break; } edge.longFlag = false; // Condition 7. } // // Fill the dominatorsMatrix with the dominators of all the nodes in this graph. // Each row is a bitset of the dominators' dfsNum for the ControlNode which dfsNum is the row index. // dominatorsMatrix must have (nNodes + 1) rows & nNodes columns. (One extra row is used for // temporary calculations). // void ControlNodeScheduler::findDominators() { // Initially each ControlNode is dominated by all the other ControlNodes. dominatorsMatrix.set(); // The Begin Node is initialized independently as it is only dominated by itself. dominatorsMatrix.clearRow(0); dominatorsMatrix.set(0, 0); bool changed; // loop condition. do { changed = false; for (Uint32 n = 1; n < nNodes; n++) { dominatorsMatrix.setRow(nNodes); // The dominators of a ControlNode are the intersection of the dominators of its // predecessors plus itself. const DoublyLinkedList& predecessors = dfsList[n]->getPredecessors(); for (DoublyLinkedList::iterator i = predecessors.begin(); !predecessors.done(i); i = predecessors.advance(i)) dominatorsMatrix.andRows(predecessors.get(i).getSource().dfsNum, nNodes); dominatorsMatrix.set(nNodes, n); if (!dominatorsMatrix.compareRows(nNodes, n)) { changed = true; dominatorsMatrix.copyRows(nNodes, n); } } } while(changed); } // // Find the loop headers in the given array of ControlNodes & return the number // of headers found. // Uint32 ControlNodeScheduler::findLoopHeaders() { Uint32 nLoopHeaders = 0; // Number of loop headers in this graph. findDominators(); for (Uint32 n = 0; n < nNodes; n++) { ControlNode& node = *dfsList[n]; Uint32 nodeIndex = node.dfsNum; const DoublyLinkedList& predecessors = node.getPredecessors(); for (DoublyLinkedList::iterator i = predecessors.begin(); !predecessors.done(i); i = predecessors.advance(i)) { Uint32 predecessorIndex = predecessors.get(i).getSource().dfsNum; // A loop header is a node with an incoming backward edge. This edge's source must be dominated // by itself (called regular backward edge). if ((predecessorIndex >= nodeIndex) && dominatorsMatrix.test(predecessorIndex, nodeIndex)) { loopHeaders.set(nodeIndex); nLoopHeaders++; break; } } } if (!loopHeaders.test(0)) { // The begin node is always considered to be a loop header (even if it's not). loopHeaders.set(0); nLoopHeaders++; } return nLoopHeaders; } // // Build the LoopHierarchyTree, find the nodes for each loop and return the top of the tree. // LoopHierarchyNode& ControlNodeScheduler::buildLoopHierarchyTree() { Uint32* nodeStack = new(pool) Uint32[nNodes]; // Stack of nodes to look at. Uint32 nLoopHeaders = findLoopHeaders(); LoopHierarchyNode* loopNodes = new(pool) LoopHierarchyNode[nLoopHeaders]; LoopHierarchySet loopSet(pool, loopNodes, nNodes); // Set of the loop headers in this graph. // If the BeginNode is considered to be a loop then its body // contains all the nodes in this graph. LoopHierarchyNode& beginNode = loopSet[0]; beginNode.header = 0; beginNode.nodes.sizeTo(pool, nNodes); beginNode.nodes.set(0, nNodes - 1); for (Int32 h = loopHeaders.nextOne(0); h != -1; h = loopHeaders.nextOne(h)) { // // Place this loop in the tree. // FastBitSet dominators(dominatorsMatrix.getRow(h), nNodes); Int32 parent = h; do // parent must be a loop header && this node must be included in the parent's loop nodes. parent = dominators.previousOne(parent); while (!(loopHeaders.test(parent) && loopSet[parent].nodes.test(h))); loopSet[parent].addSuccessor(loopSet[h]); // // Find this loop's nodes. // Uint32* stackPointer = nodeStack; LoopHierarchyNode& loop = loopSet[h]; // Initialize the loop header's variables. loop.header = h; loop.nodes.sizeToAndClear(pool, nNodes); loop.nodes.set(h); // Push the tails of this loop. Each tail must be dominated by the loop header. const DoublyLinkedList& headerPredecessors = dfsList[h]->getPredecessors(); for (DoublyLinkedList::iterator i = headerPredecessors.begin(); !headerPredecessors.done(i); i = headerPredecessors.advance(i)) { Uint32 predecessorIndex = headerPredecessors.get(i).getSource().dfsNum; if ((predecessorIndex > Uint32(h)) && dominatorsMatrix.test(predecessorIndex, h)) { loop.nodes.set(predecessorIndex); *stackPointer++ = predecessorIndex; } } while (stackPointer != nodeStack) { Uint32 n = *--stackPointer; const DoublyLinkedList& predecessors = dfsList[n]->getPredecessors(); for (DoublyLinkedList::iterator i = predecessors.begin(); !predecessors.done(i); i = predecessors.advance(i)) { Uint32 predecessorIndex = predecessors.get(i).getSource().dfsNum; if (!loop.nodes.test(predecessorIndex)) { loop.nodes.set(predecessorIndex); *stackPointer++ = predecessorIndex; } } } } return loopSet[0]; } // // Return an array of scheduled nodes. This array has nNodes elements. // ControlNode** ControlNodeScheduler::getScheduledNodes() { // Initialize the ScheduledNodes. scheduledNodes = new(pool) ScheduledNode[nNodes]; for (Uint32 i = 0; i < nNodes; i++) { ScheduledNode& node = scheduledNodes[i]; node.dfsNum = i; } nodesAlreadyScheduled.sizeToAndClear(nNodes); // // Determine main nodes (called working nodes). // Uint32 generation = ControlNode::getNextGeneration(); Uint32* nodeStack = new(pool) Uint32[nNodes]; // Stack of nodes to look at. Uint32* stackPointer = nodeStack; *stackPointer++ = 0; // Push the beginNode. while (stackPointer != nodeStack) { Uint32 n = *--stackPointer; ControlNode& node = *dfsList[n]; node.workingNode = true; ControlEdge* limit = node.getSuccessorsEnd(); for (ControlEdge* edge = node.getSuccessorsBegin(); edge != limit; edge++) { ControlNode& successor = edge->getTarget(); if (successor.generation != generation) { switch(successor.getControlKind()) { case ckCatch: case ckEnd: // We are not following the exception edges. break; default: successor.generation = generation; *stackPointer++ = successor.dfsNum; break; } } } } // // Schedule the loops from the inside out. // struct EdgeStack { DoublyLinkedList* linkedList; DoublyLinkedList::iterator position; }; LoopHierarchyNode& top = buildLoopHierarchyTree(); EdgeStack *edgeStack = new(pool) EdgeStack[nNodes]; DoublyLinkedList edgeToBeginNodeList; edgeToBeginNodeList.addLast(top); DoublyLinkedList* currentList = &edgeToBeginNodeList; DoublyLinkedList::iterator currentIterator = edgeToBeginNodeList.begin(); EdgeStack *edgeStackPointer = edgeStack; while (true) { if (currentList->done(currentIterator)) { if (edgeStackPointer == edgeStack) break; --edgeStackPointer; currentList = edgeStackPointer->linkedList; currentIterator = edgeStackPointer->position; // At this point all the loops below this one in the loop hierarchy tree have // been scheduled. LoopHierarchyNode& loopToSchedule = currentList->get(currentIterator->prev); scheduleLoop(loopToSchedule); } else { LoopHierarchyNode& loop = currentList->get(currentIterator); currentIterator = currentList->advance(currentIterator); edgeStackPointer->linkedList = currentList; edgeStackPointer->position = currentIterator; edgeStackPointer++; currentList = &loop.getSuccessors(); currentIterator = currentList->begin(); } } // FIXME: need to schedule the remaining nodes (exceptions). // Summarize the scheduling. ControlNode** result = new(pool) ControlNode*[nNodes]; ControlNode** ptr = result; for (DoublyLinkedList::iterator c = top.cliques.begin(); !top.cliques.done(c); c = top.cliques.advance(c)) { Clique& clique = top.cliques.get(c); for (DoublyLinkedList::iterator s = clique.begin(); !clique.done(s); s = clique.advance(s)) *ptr++ = dfsList[clique.get(s).dfsNum]; } // Add the endNode. *ptr++ = dfsList[nNodes - 1]; PR_ASSERT(ptr == &result[nNodes]); return result; }