зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0e8d2faad2
Коммит
1159281622
|
@ -146,6 +146,7 @@ CPPSRCS = \
|
|||
Memory.cpp \
|
||||
Statistics.cpp \
|
||||
StoreBuffer.cpp \
|
||||
FindSCCs.cpp \
|
||||
StringBuffer.cpp \
|
||||
Unicode.cpp \
|
||||
Xdr.cpp \
|
||||
|
|
|
@ -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 */
|
|
@ -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___ */
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
Загрузка…
Ссылка в новой задаче