Unify the two DFG cycle finding algorithms. (#6262)

Both V3DfgBreakCycles.cpp and V3DfgDecomposition.cpp used to contain an
implementation of the same algorithm to color strongly connected
components. Now there is only one, and it lives in V3DfgColorSCCs.cpp.
This commit is contained in:
Geza Lore
2025-08-05 13:03:30 +01:00
committed by GitHub
parent 4a5935d248
commit 53332ae03e
6 changed files with 235 additions and 331 deletions
+156
View File
@@ -0,0 +1,156 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Cycle finding algorithm for DfgGraph
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Implements Pearce's algorithm to color the strongly connected components. For reference
// see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed
// Graph", David J.Pearce, 2005.
//
//*************************************************************************
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include <limits>
#include <vector>
// Similar algorithm used in ExtractCyclicComponents.
// This one sets DfgVertex::user(). See the static 'apply' method below.
class ColorStronglyConnectedComponents final {
static constexpr uint32_t UNASSIGNED = std::numeric_limits<uint32_t>::max();
// TYPES
struct VertexState final {
uint32_t component = UNASSIGNED; // Result component number (0 means not in SCC)
uint32_t index = UNASSIGNED; // Used by Pearce's algorithm for detecting SCCs
VertexState() = default;
VertexState(uint32_t i, uint32_t n)
: component{n}
, index{i} {}
};
// STATE
DfgGraph& m_dfg; // The input graph
uint32_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph
uint32_t m_index = 0; // Visitation index counter
std::vector<DfgVertex*> m_stack; // The stack used by the algorithm
// METHODS
void visitColorSCCs(DfgVertex& vtx, VertexState& vtxState) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED, &vtx, "Already visited vertex"););
// Visiting vertex
const size_t rootIndex = vtxState.index = ++m_index;
// Visit children
vtx.forEachSink([&](DfgVertex& child) {
VertexState& childSatate = child.user<VertexState>();
// If the child has not yet been visited, then continue traversal
if (childSatate.index == UNASSIGNED) visitColorSCCs(child, childSatate);
// If the child is not in an SCC
if (childSatate.component == UNASSIGNED) {
if (vtxState.index > childSatate.index) vtxState.index = childSatate.index;
}
});
if (vtxState.index == rootIndex) {
// This is the 'root' of an SCC
// A trivial SCC contains only a single vertex
const bool isTrivial = m_stack.empty() //
|| m_stack.back()->getUser<VertexState>().index < rootIndex;
// We also need a separate component for vertices that drive themselves (which can
// happen for input like 'assign a = a'), as we want to extract them (they are cyclic).
const bool drivesSelf = vtx.findSink<DfgVertex>([&vtx](const DfgVertex& sink) { //
return &vtx == &sink;
});
if (!isTrivial || drivesSelf) {
// Allocate new component
++m_nonTrivialSCCs;
vtxState.component = m_nonTrivialSCCs;
while (!m_stack.empty()) {
VertexState& topState = m_stack.back()->getUser<VertexState>();
// Only higher nodes belong to the same SCC
if (topState.index < rootIndex) break;
m_stack.pop_back();
topState.component = m_nonTrivialSCCs;
}
} else {
// Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph.
vtxState.component = 0;
}
} else {
// Not the root of an SCC
m_stack.push_back(&vtx);
}
}
void colorSCCs() {
// We know constant nodes have no input edges, so they cannot be part
// of a non-trivial SCC. Mark them as such without any real traversals.
for (DfgConst& vtx : m_dfg.constVertices()) vtx.setUser(VertexState{0, 0});
// Start traversals through variables
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If it has no input or no outputs, it cannot be part of a non-trivial SCC.
if (vtx.arity() == 0 || !vtx.hasSinks()) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED || vtxState.component == 0,
&vtx, "Non circular variable must be in a trivial SCC"););
vtxState.index = 0;
vtxState.component = 0;
continue;
}
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
}
// Start traversals through operations
for (DfgVertex& vtx : m_dfg.opVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
}
}
ColorStronglyConnectedComponents(DfgGraph& dfg)
: m_dfg{dfg} {
UASSERT(dfg.size() < UNASSIGNED, "Graph too big " << dfg.name());
// Yet another implementation of Pearce's algorithm.
colorSCCs();
// Re-assign user values
m_dfg.forEachVertex([](DfgVertex& vtx) {
const uint64_t component = vtx.getUser<VertexState>().component;
vtx.setUser<uint64_t>(component);
});
}
public:
// Sets DfgVertex::user<uint64_t>() for all vertext to:
// - 0, if the vertex is not part of a non-trivial strongly connected component
// and is not part of a self-loop. That is: the Vertex is not part of any cycle.
// - N, if the vertex is part of a non-trivial strongly conneced component or self-loop N.
// That is: each set of vertices that are reachable from each other will have the same
// non-zero value assigned.
// Returns the number of non-trivial SCCs (~distinct cycles)
static uint32_t apply(DfgGraph& dfg) {
return ColorStronglyConnectedComponents{dfg}.m_nonTrivialSCCs;
}
};
uint32_t V3DfgPasses::colorStronglyConnectedComponents(DfgGraph& dfg) {
return ColorStronglyConnectedComponents::apply(dfg);
}