mirror of
https://github.com/YosysHQ/yosys
synced 2025-04-06 17:44:09 +00:00
Add generic topological sort and SCC detection
This adds a generic non-recursive implementation of Tarjan's linear time SCC algorithm that produces components in topological order. It can be instantiated to work directly on any graph representation for which the enumerate_nodes and enumerate_successors interface can be implemented.
This commit is contained in:
parent
4cddc19994
commit
0922142567
328
kernel/topo_scc.h
Normal file
328
kernel/topo_scc.h
Normal file
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TOPO_SCC_H
|
||||
#define TOPO_SCC_H
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
class SigCellGraph {
|
||||
public:
|
||||
typedef int node_type;
|
||||
|
||||
struct successor_enumerator {
|
||||
std::vector<std::pair<int, int>>::const_iterator current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current->second;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct node_enumerator {
|
||||
int current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
idict<RTLIL::Cell *> cell_ids;
|
||||
idict<RTLIL::SigBit> sig_ids;
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> indices_;
|
||||
int offset;
|
||||
bool computed = false;
|
||||
|
||||
void compute() {
|
||||
offset = GetSize(sig_ids);
|
||||
edge_ranges.clear();
|
||||
indices_.clear();
|
||||
indices_.resize(GetSize(sig_ids) + GetSize(cell_ids), -1);
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
auto last = std::unique(edges.begin(), edges.end());
|
||||
edges.erase(last, edges.end());
|
||||
auto edge = edges.begin();
|
||||
auto edge_end = edges.end();
|
||||
int range_begin = 0;
|
||||
for (int node = -offset, node_end = GetSize(cell_ids); node != node_end; ++node) {
|
||||
while (edge != edge_end && edge->first <= node)
|
||||
++edge;
|
||||
int range_end = edge - edges.begin();
|
||||
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
||||
range_begin = range_end;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
node_type node(RTLIL::Cell *cell) { return cell_ids(cell); }
|
||||
node_type node(SigBit const &bit) { return ~sig_ids(bit); }
|
||||
|
||||
bool is_cell(node_type node) { return node >= 0; }
|
||||
bool is_sig(node_type node) { return node < 0; }
|
||||
|
||||
Cell *cell(node_type node) { return node >= 0 ? cell_ids[node] : nullptr; }
|
||||
SigBit sig(node_type node) { return node < 0 ? sig_ids[~node] : SigBit(); }
|
||||
|
||||
template<typename Src, typename Dst>
|
||||
void add_edge(Src &&src, Dst &&dst) {
|
||||
computed = false;
|
||||
node_type src_node = node(std::forward<Src>(src));
|
||||
node_type dst_node = node(std::forward<Dst>(dst));
|
||||
edges.emplace_back(std::make_pair(src_node, dst_node));
|
||||
}
|
||||
|
||||
node_enumerator enumerate_nodes() {
|
||||
if (!computed) compute();
|
||||
return {-GetSize(sig_ids), GetSize(cell_ids)};
|
||||
}
|
||||
|
||||
successor_enumerator enumerate_successors(node_type const &node) const {
|
||||
auto range = edge_ranges[node + offset];
|
||||
return {edges.begin() + range.first, edges.begin() + range.second};
|
||||
}
|
||||
|
||||
int &dfs_index(node_type const &node) {
|
||||
return indices_[node + offset];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class IntGraph {
|
||||
public:
|
||||
typedef int node_type;
|
||||
|
||||
struct successor_enumerator {
|
||||
std::vector<std::pair<int, int>>::const_iterator current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current->second;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct node_enumerator {
|
||||
int current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> indices_;
|
||||
bool computed = false;
|
||||
|
||||
void compute() {
|
||||
edge_ranges.clear();
|
||||
|
||||
int node_end = 0;
|
||||
for (auto const &edge : edges)
|
||||
node_end = std::max(node_end, std::max(edge.first, edge.second) + 1);
|
||||
indices_.clear();
|
||||
indices_.resize(node_end, -1);
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
auto last = std::unique(edges.begin(), edges.end());
|
||||
edges.erase(last, edges.end());
|
||||
auto edge = edges.begin();
|
||||
auto edge_end = edges.end();
|
||||
int range_begin = 0;
|
||||
for (int node = 0; node != node_end; ++node) {
|
||||
while (edge != edge_end && edge->first <= node)
|
||||
++edge;
|
||||
int range_end = edge - edges.begin();
|
||||
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
||||
range_begin = range_end;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void add_edge(int src, int dst) {
|
||||
log_assert(src >= 0);
|
||||
log_assert(dst >= 0);
|
||||
computed = false;
|
||||
edges.emplace_back(std::make_pair(src, dst));
|
||||
}
|
||||
|
||||
node_enumerator enumerate_nodes() {
|
||||
if (!computed) compute();
|
||||
return {0, GetSize(indices_)};
|
||||
}
|
||||
|
||||
successor_enumerator enumerate_successors(int node) const {
|
||||
auto range = edge_ranges[node];
|
||||
return {edges.begin() + range.first, edges.begin() + range.second};
|
||||
}
|
||||
|
||||
int &dfs_index(node_type const &node) {
|
||||
return indices_[node];
|
||||
}
|
||||
};
|
||||
|
||||
template<typename G, typename ComponentCallback>
|
||||
void topo_sorted_sccs(G &graph, ComponentCallback component)
|
||||
{
|
||||
typedef typename G::node_enumerator node_enumerator;
|
||||
typedef typename G::successor_enumerator successor_enumerator;
|
||||
typedef typename G::node_type node_type;
|
||||
|
||||
struct dfs_entry {
|
||||
node_type node;
|
||||
successor_enumerator successors;
|
||||
int lowlink;
|
||||
|
||||
dfs_entry(node_type node, successor_enumerator successors, int lowlink) :
|
||||
node(node), successors(successors), lowlink(lowlink)
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<dfs_entry> dfs_stack;
|
||||
std::vector<node_type> component_stack;
|
||||
int next_index = 0;
|
||||
|
||||
|
||||
node_enumerator nodes = graph.enumerate_nodes();
|
||||
|
||||
// iterate over all nodes to ensure we process the whole graph
|
||||
while (!nodes.finished()) {
|
||||
node_type node = nodes.next();
|
||||
// only start a new search if the node wasn't visited yet
|
||||
if (graph.dfs_index(node) >= 0)
|
||||
continue;
|
||||
|
||||
while (true) {
|
||||
// at this point we're visiting the node for the first time during
|
||||
// the DFS search
|
||||
|
||||
// we record the timestamp of when we first visited the node as the
|
||||
// dfs_index
|
||||
int lowlink = next_index;
|
||||
next_index++;
|
||||
graph.dfs_index(node) = lowlink;
|
||||
|
||||
// and we add the node to the component stack where it will remain
|
||||
// until all nodes of the component containing this node are popped
|
||||
component_stack.push_back(node);
|
||||
|
||||
// then we start iterating over the successors of this node
|
||||
successor_enumerator successors = graph.enumerate_successors(node);
|
||||
while (true) {
|
||||
if (successors.finished()) {
|
||||
// when we processed all successors, i.e. when we visited
|
||||
// the complete DFS subtree rooted at the current node, we
|
||||
// first check whether the current node is a SCC root
|
||||
//
|
||||
// (why this check identifies SCC roots is out of scope for
|
||||
// this comment, see other material on Tarjan's SCC
|
||||
// algorithm)
|
||||
if (lowlink == graph.dfs_index(node)) {
|
||||
// the SCC containing the current node is at the top of
|
||||
// the component stack, with the current node at the bottom
|
||||
int current = GetSize(component_stack);
|
||||
do {
|
||||
--current;
|
||||
} while (component_stack[current] != node);
|
||||
|
||||
// we invoke the callback with a pointer range of the
|
||||
// nodes in the SCC
|
||||
|
||||
node_type *stack_ptr = component_stack.data();
|
||||
node_type *component_begin = stack_ptr + current;
|
||||
node_type *component_end = stack_ptr + component_stack.size();
|
||||
|
||||
// note that we allow the callback to permute the nodes
|
||||
// in this range as well as to modify dfs_index of the
|
||||
// nodes in the SCC.
|
||||
component(component_begin, component_end);
|
||||
|
||||
// by setting the dfs_index of all already emitted
|
||||
// nodes to INT_MAX, we don't need a separate check for
|
||||
// whether successor nodes are still on the component
|
||||
// stack before updating the lowlink value
|
||||
for (; component_begin != component_end; ++component_begin)
|
||||
graph.dfs_index(*component_begin) = INT_MAX;
|
||||
component_stack.resize(current);
|
||||
}
|
||||
|
||||
// after checking for a completed SCC the DFS either
|
||||
// continues the search at the parent node or returns to
|
||||
// the outer loop if we already are at the root node.
|
||||
if (dfs_stack.empty())
|
||||
goto next_search;
|
||||
auto &dfs_top = dfs_stack.back();
|
||||
|
||||
node = dfs_top.node;
|
||||
successors = std::move(dfs_top.successors);
|
||||
|
||||
// the parent's lowlink is updated when returning
|
||||
lowlink = min(lowlink, dfs_top.lowlink);
|
||||
dfs_stack.pop_back();
|
||||
// continue checking the remaining successors of the parent node.
|
||||
} else {
|
||||
node_type succ = successors.next();
|
||||
if (graph.dfs_index(succ) < 0) {
|
||||
// if the successor wasn't visted yet, the DFS recurses
|
||||
// into the successor
|
||||
|
||||
// we save the state for this node and make the
|
||||
// successor the current node.
|
||||
dfs_stack.emplace_back(node, std::move(successors), lowlink);
|
||||
node = succ;
|
||||
|
||||
// this break gets us to the section corresponding to
|
||||
// the function entry in the recursive version
|
||||
break;
|
||||
} else {
|
||||
// the textbook version guards this update with a check
|
||||
// whether the successor is still on the component
|
||||
// stack. If the successor node was already visisted
|
||||
// but is not on the component stack, it must be part
|
||||
// of an already emitted SCC. We can avoid this check
|
||||
// by setting the DFS index of all nodes in a SCC to
|
||||
// INT_MAX when the SCC is emitted.
|
||||
lowlink = min(lowlink, graph.dfs_index(succ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
next_search:;
|
||||
}
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue