Bug 790338 - Implement Tarjan's algorithm to find the stongly connected components of the compartment graph r=billm

--HG--
rename : accessible/src/windows/sdn/sdnAccessible.cpp => accessible/src/msaa/nsAccessNodeWrap.cpp
rename : accessible/src/windows/sdn/sdnAccessible.h => accessible/src/msaa/nsAccessNodeWrap.h
extra : rebase_source : f529480202322726c55c23e40529c81092c5b6c3
This commit is contained in:
Jon Coppeard 2012-10-12 10:45:29 +01:00
parent abbf0a01f4
commit 7cff883a03
5 changed files with 572 additions and 0 deletions

View File

@ -146,6 +146,7 @@ CPPSRCS = \
Memory.cpp \
Statistics.cpp \
StoreBuffer.cpp \
FindSCCs.cpp \
StringBuffer.cpp \
Unicode.cpp \
Xdr.cpp \

176
js/src/gc/FindSCCs.cpp Normal file
View File

@ -0,0 +1,176 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FindSCCs.h"
#include "jsfriendapi.h"
namespace js {
namespace gc {
ComponentFinder::ComponentFinder(uintptr_t sl)
: clock(1),
stack(NULL),
firstComponent(NULL),
cur(NULL),
stackLimit(sl),
stackFull(false)
{
}
ComponentFinder::~ComponentFinder()
{
JS_ASSERT(!stack);
JS_ASSERT(!firstComponent);
}
void
ComponentFinder::addNode(GraphNodeBase *v)
{
if (v->gcDiscoveryTime == Undefined) {
JS_ASSERT(v->gcLowLink == Undefined);
JS_ASSERT(!v->gcNextGraphNode);
processNode(v);
}
}
void
ComponentFinder::checkStackFull()
{
/*
* Check for exceeding the size of the C stack.
*
* If this happens we give up and return all vertices in one group, by
* pushing them onto the output list with lowLink set to 1.
*/
if (!stackFull) {
int stackDummy;
if (!JS_CHECK_STACK_SIZE(stackLimit, &stackDummy))
stackFull = true;
}
if (stackFull) {
GraphNodeBase *w;
while (stack) {
w = stack;
stack = w->gcNextGraphNode;
w->gcLowLink = 1;
w->gcNextGraphNode = firstComponent;
firstComponent = w;
}
}
}
void
ComponentFinder::processNode(GraphNodeBase *v)
{
v->gcDiscoveryTime = clock;
v->gcLowLink = clock;
++clock;
JS_ASSERT(!v->gcNextGraphNode);
v->gcNextGraphNode = stack;
stack = v;
checkStackFull();
if (stackFull)
return;
GraphNodeBase *old = cur;
cur = v;
cur->findOutgoingEdges(*this);
cur = old;
if (stackFull) {
JS_ASSERT(!stack);
return;
}
if (v->gcLowLink == v->gcDiscoveryTime) {
GraphNodeBase *w;
do {
JS_ASSERT(stack);
w = stack;
stack = w->gcNextGraphNode;
/*
* Record the elements of a component by setting all their gcLowLink
* fields to the same value.
*/
w->gcLowLink = v->gcDiscoveryTime;
/*
* Record that the element is no longer on the stack by setting the
* discovery time to a special value that's not Undefined.
*/
w->gcDiscoveryTime = Finished;
/*
* Prepend the component to the beginning of the output list to
* reverse the list and achieve the desired order.
*/
w->gcNextGraphNode = firstComponent;
firstComponent = w;
} while (w != v);
}
}
void
ComponentFinder::addEdgeTo(GraphNodeBase *w)
{
if (w->gcDiscoveryTime == Undefined) {
processNode(w);
cur->gcLowLink = Min(cur->gcLowLink, w->gcLowLink);
} else if (w->gcDiscoveryTime != Finished) {
cur->gcLowLink = Min(cur->gcLowLink, w->gcDiscoveryTime);
}
}
GraphNodeBase *
ComponentFinder::getResultsList()
{
JS_ASSERT(!stack);
GraphNodeBase *result = firstComponent;
firstComponent = NULL;
return result;
}
GraphNodeBase *
ComponentFinder::removeFirstGroup(GraphNodeBase *resultsList)
{
/* Remove the first group from resultsList and return the new list head. */
JS_ASSERT(resultsList);
GraphNodeBase *v = resultsList;
unsigned lowLink = v->gcLowLink;
GraphNodeBase *last;
do {
v->gcDiscoveryTime = Undefined;
v->gcLowLink = Undefined;
last = v;
v = v->gcNextGraphNode;
}
while (v && v->gcLowLink == lowLink);
last->gcNextGraphNode = NULL;
return v;
}
void
ComponentFinder::removeAllRemaining(GraphNodeBase *resultsList)
{
for (GraphNodeBase *v = resultsList; v; v = v->gcNextGraphNode) {
v->gcDiscoveryTime = Undefined;
v->gcLowLink = Undefined;
}
}
} /* namespace gc */
} /* namespace js */

131
js/src/gc/FindSCCs.h Normal file
View File

@ -0,0 +1,131 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef gc_findsccs_h___
#define gc_findsccs_h___
#include "jsutil.h"
namespace js {
namespace gc {
class ComponentFinder;
struct GraphNodeBase {
GraphNodeBase *gcNextGraphNode;
unsigned gcDiscoveryTime;
unsigned gcLowLink;
GraphNodeBase()
: gcNextGraphNode(NULL),
gcDiscoveryTime(0),
gcLowLink(0) {}
virtual ~GraphNodeBase() {}
virtual void findOutgoingEdges(ComponentFinder& finder) = 0;
};
template <class T> static T *
NextGraphNode(const T *current)
{
const GraphNodeBase *node = current;
return static_cast<T *>(node->gcNextGraphNode);
}
template <class T> void
AddGraphNode(T *&listHead, T *newFirstNode)
{
GraphNodeBase *node = newFirstNode;
JS_ASSERT(!node->gcNextGraphNode);
node->gcNextGraphNode = listHead;
listHead = newFirstNode;
}
template <class T> static T *
RemoveGraphNode(T *&listHead)
{
GraphNodeBase *node = listHead;
if (!node)
return NULL;
T *result = listHead;
listHead = static_cast<T *>(node->gcNextGraphNode);
node->gcNextGraphNode = NULL;
return result;
}
/*
* Find the strongly connected components of a graph using Tarjan's algorithm,
* and return them in topological order.
*
* Nodes derive from GraphNodeBase and implement findGraphEdges, which calls
* finder.addEdgeTo to describe the outgoing edges from that node:
*
* struct MyGraphNode : public GraphNodeBase
* {
* void findOutgoingEdges(ComponentFinder& finder)
* {
* for edge in my_outgoing_edges:
* if is_relevant(edge):
* finder.addEdgeTo(edge.destination)
* }
* }
*/
class ComponentFinder
{
public:
ComponentFinder(uintptr_t stackLimit);
~ComponentFinder();
void addNode(GraphNodeBase *v);
GraphNodeBase *getResultsList();
template <class T> static T *
getNextGroup(T *&resultsList) {
T *group = resultsList;
if (resultsList)
resultsList = static_cast<T *>(removeFirstGroup(resultsList));
return group;
}
template <class T> static T *
getAllRemaining(T *&resultsList) {
T *all = resultsList;
removeAllRemaining(resultsList);
resultsList = NULL;
return all;
}
private:
static GraphNodeBase *removeFirstGroup(GraphNodeBase *resultsList);
static void removeAllRemaining(GraphNodeBase *resultsList);
public:
/* Call from implementation of GraphNodeBase::findOutgoingEdges(). */
void addEdgeTo(GraphNodeBase *w);
private:
/* Constant used to indicate an unprocessed vertex. */
static const unsigned Undefined = 0;
/* Constant used to indicate an processed vertex that is no longer on the stack. */
static const unsigned Finished = (unsigned)-1;
void processNode(GraphNodeBase *v);
void checkStackFull();
private:
unsigned clock;
GraphNodeBase *stack;
GraphNodeBase *firstComponent;
GraphNodeBase *cur;
uintptr_t stackLimit;
bool stackFull;
};
} /* namespace gc */
} /* namespace js */
#endif /* gc_findsccs_h___ */

View File

@ -67,6 +67,7 @@ CPPSRCS = \
testJSEvaluateScript.cpp \
testErrorCopying.cpp \
testEnclosingFunction.cpp \
testFindSCCs.cpp \
$(NULL)
# Disabled: an entirely unrelated test seems to cause this to fail. Moreover,

View File

@ -0,0 +1,263 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "tests.h"
#include <string.h>
#include <stdarg.h>
#include "../gc/FindSCCs.h"
#include "jscntxt.h"
#include "jsgc.h"
static const unsigned MaxVertices = 10;
using js::gc::GraphNodeBase;
using js::gc::ComponentFinder;
struct TestNode : public GraphNodeBase
{
unsigned index;
bool hasEdge[MaxVertices];
void findOutgoingEdges(ComponentFinder& finder);
};
static TestNode Vertex[MaxVertices];
void
TestNode::findOutgoingEdges(ComponentFinder& finder)
{
for (unsigned i = 0; i < MaxVertices; ++i) {
if (hasEdge[i])
finder.addEdgeTo(&Vertex[i]);
}
}
BEGIN_TEST(testFindSCCs)
{
// no vertices
setup(0);
run();
CHECK(end());
// no edges
setup(1);
run();
CHECK(group(0, -1));
CHECK(end());
setup(3);
run();
CHECK(group(2, -1));
CHECK(group(1, -1));
CHECK(group(0, -1));
CHECK(end());
// linear
setup(3);
edge(0, 1);
edge(1, 2);
run();
CHECK(group(0, -1));
CHECK(group(1, -1));
CHECK(group(2, -1));
CHECK(end());
// tree
setup(3);
edge(0, 1);
edge(0, 2);
run();
CHECK(group(0, -1));
CHECK(group(2, -1));
CHECK(group(1, -1));
CHECK(end());
// cycles
setup(3);
edge(0, 1);
edge(1, 2);
edge(2, 0);
run();
CHECK(group(0, 1, 2, -1));
CHECK(end());
setup(4);
edge(0, 1);
edge(1, 2);
edge(2, 1);
edge(2, 3);
run();
CHECK(group(0, -1));
CHECK(group(1, 2, -1));
CHECK(group(3, -1));
CHECK(end());
// remaining
setup(2);
edge(0, 1);
run();
CHECK(remaining(0, 1, -1));
CHECK(end());
setup(2);
edge(0, 1);
run();
CHECK(group(0, -1));
CHECK(remaining(1, -1));
CHECK(end());
setup(2);
edge(0, 1);
run();
CHECK(group(0, -1));
CHECK(group(1, -1));
CHECK(remaining(-1));
CHECK(end());
return true;
}
unsigned vertex_count;
ComponentFinder *finder;
GraphNodeBase *resultsList;
void setup(unsigned count)
{
vertex_count = count;
for (unsigned i = 0; i < MaxVertices; ++i) {
TestNode &v = Vertex[i];
v.gcNextGraphNode = NULL;
v.index = i;
memset(&v.hasEdge, 0, sizeof(v.hasEdge));
}
}
void edge(unsigned src_index, unsigned dest_index)
{
Vertex[src_index].hasEdge[dest_index] = true;
}
void run()
{
finder = new ComponentFinder(rt->nativeStackLimit);
for (unsigned i = 0; i < vertex_count; ++i)
finder->addNode(&Vertex[i]);
resultsList = finder->getResultsList();
}
bool group(int vertex, ...)
{
TestNode *v = (TestNode *)ComponentFinder::getNextGroup(resultsList);
va_list ap;
va_start(ap, vertex);
while (vertex != -1) {
CHECK(v != NULL);
CHECK(v->index == vertex);
v = (TestNode *)v->gcNextGraphNode;
vertex = va_arg(ap, int);
}
va_end(ap);
CHECK(v == NULL);
return true;
}
bool remaining(int vertex, ...)
{
TestNode *v = (TestNode *)ComponentFinder::getAllRemaining(resultsList);
va_list ap;
va_start(ap, vertex);
while (vertex != -1) {
CHECK(v != NULL);
CHECK(v->index == vertex);
v = (TestNode *)v->gcNextGraphNode;
vertex = va_arg(ap, int);
}
va_end(ap);
CHECK(v == NULL);
return true;
}
bool end()
{
CHECK(resultsList == NULL);
delete finder;
finder = NULL;
return true;
}
END_TEST(testFindSCCs)
struct TestNode2 : public GraphNodeBase
{
TestNode2 *edge;
TestNode2() :
edge(NULL)
{
}
void
findOutgoingEdges(ComponentFinder& finder)
{
if (edge)
finder.addEdgeTo(edge);
}
};
BEGIN_TEST(testFindSCCsStackLimit)
{
/*
* Test what happens if recusion causes the stack to become full while
* traversing the graph.
*
* The test case is a large number of vertices, almost all of which are
* arranged in a linear chain. The last few are left unlinked to exercise
* adding vertices after the stack full condition has already been detected.
*
* Such an arrangement with no cycles would normally result in one group for
* each vertex, but since the stack is exhasted in processing a single group
* is returned containing all the vertices.
*/
const unsigned max = 1000000;
TestNode2 *vertices = new TestNode2[max]();
for (unsigned i = 0; i < (max - 10); ++i)
vertices[i].edge = &vertices[i + 1];
ComponentFinder finder(rt->nativeStackLimit);
for (unsigned i = 0; i < max; ++i)
finder.addNode(&vertices[i]);
GraphNodeBase *r = finder.getResultsList();
CHECK(r);
GraphNodeBase *v = finder.getNextGroup(r);
CHECK(v);
unsigned count = 0;
while (v) {
++count;
v = v->gcNextGraphNode;
}
CHECK(count == max);
CHECK(finder.getNextGroup(r) == NULL);
delete [] vertices;
return true;
}
END_TEST(testFindSCCsStackLimit)