// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Graph optimizations // // 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 // //************************************************************************* #define VL_MT_DISABLED_CODE_UNIT 1 #include "config_build.h" #include "verilatedos.h" #include "V3GraphAlg.h" #include "V3Global.h" #include "V3GraphPathChecker.h" #include "V3GraphStream.h" #include "V3Stats.h" #include #include #include #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### //###################################################################### // Algorithms - Remove redundancies // Changes user() and weight() class GraphRemoveRedundant final : GraphAlg<> { const bool m_sumWeights; ///< Sum, rather then maximize weights private: void main() { for (V3GraphVertex& vertex : m_graphp->vertices()) vertexIterate(&vertex); } void vertexIterate(V3GraphVertex* vertexp) { // Clear marks for (V3GraphEdge& edge : vertexp->outEdges()) edge.top()->userp(nullptr); // Mark edges and detect duplications for (V3GraphEdge* const edgep : vertexp->outEdges().unlinkable()) { if (followEdge(edgep)) { V3GraphVertex* outVertexp = edgep->top(); V3GraphEdge* prevEdgep = static_cast(outVertexp->userp()); if (!prevEdgep) { // No previous assignment outVertexp->userp(edgep); } else { // Duplicate bool saveOld = true; if (prevEdgep->cutable() && !edgep->cutable()) { saveOld = false; // new !cutable more important than old } else if (!prevEdgep->cutable() && edgep->cutable()) { saveOld = true; // old !cutable more important than new } else { saveOld = true; if (!m_sumWeights && (prevEdgep->weight() < edgep->weight())) { // Keep max weight prevEdgep->weight(edgep->weight()); } } if (saveOld) { if (m_sumWeights) prevEdgep->weight(prevEdgep->weight() + edgep->weight()); VL_DO_DANGLING(edgep->unlinkDelete(), edgep); } else { if (m_sumWeights) edgep->weight(prevEdgep->weight() + edgep->weight()); VL_DO_DANGLING(prevEdgep->unlinkDelete(), prevEdgep); outVertexp->userp(edgep); } } } } } public: GraphRemoveRedundant(V3Graph* graphp, V3EdgeFuncP edgeFuncp, bool sumWeights) : GraphAlg<>{graphp, edgeFuncp} , m_sumWeights{sumWeights} { main(); } ~GraphRemoveRedundant() = default; }; void V3Graph::removeRedundantEdgesMax(V3EdgeFuncP edgeFuncp) { GraphRemoveRedundant{this, edgeFuncp, false}; } void V3Graph::removeRedundantEdgesSum(V3EdgeFuncP edgeFuncp) { GraphRemoveRedundant{this, edgeFuncp, true}; } //###################################################################### //###################################################################### // Algorithms - remove transitive class GraphAlgRemoveTransitiveEdges final : GraphAlg<> { public: explicit GraphAlgRemoveTransitiveEdges(V3Graph* graphp) : GraphAlg<>(graphp, nullptr) {} void go() { GraphPathChecker checker{m_graphp}; for (V3GraphVertex& vtx : m_graphp->vertices()) { V3GraphEdge* deletep = nullptr; for (V3GraphEdge& edge : vtx.outEdges()) { if (deletep) VL_DO_CLEAR(deletep->unlinkDelete(), deletep = nullptr); // It should be safe to modify the graph, despite using // the GraphPathChecker, as none of the modifications will // change what can be reached from what, nor should they // change the rank or CP of any node. if (checker.isTransitiveEdge(&edge)) deletep = &edge; } if (deletep) VL_DO_DANGLING(deletep->unlinkDelete(), deletep); } } private: VL_UNCOPYABLE(GraphAlgRemoveTransitiveEdges); }; void V3Graph::removeTransitiveEdges() { GraphAlgRemoveTransitiveEdges{this}.go(); } //###################################################################### //###################################################################### // Algorithms - weakly connected components // Changes color() class GraphAlgWeakly final : GraphAlg<> { void main() { // Initialize state m_graphp->clearColors(); // Color graph uint32_t currentColor = 0; for (V3GraphVertex& vertex : m_graphp->vertices()) { currentColor++; vertexIterate(&vertex, currentColor); } } void vertexIterate(V3GraphVertex* vertexp, uint32_t currentColor) { // Assign new color to each unvisited node // then visit each of its edges, giving them the same color if (vertexp->color()) return; // Already colored it vertexp->color(currentColor); for (V3GraphEdge& edge : vertexp->outEdges()) { if (followEdge(&edge)) vertexIterate(edge.top(), currentColor); } for (V3GraphEdge& edge : vertexp->inEdges()) { if (followEdge(&edge)) vertexIterate(edge.fromp(), currentColor); } } public: GraphAlgWeakly(V3Graph* graphp, V3EdgeFuncP edgeFuncp) : GraphAlg<>(graphp, edgeFuncp) { main(); } ~GraphAlgWeakly() = default; }; void V3Graph::weaklyConnected(V3EdgeFuncP edgeFuncp) { GraphAlgWeakly{this, edgeFuncp}; } //###################################################################### //###################################################################### // Algorithms - strongly connected components // Changes user() and color() class GraphAlgStrongly final : GraphAlg<> { uint32_t m_currentDfs = 0; // DFS count std::vector m_callTrace; // List of everything we hit processing so far void main() { // Use 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 // // Node State: // Vertex::user // DFS number indicating possible root of subtree, 0=not iterated // Vertex::color // Output subtree number (fully processed) // Clear info for (V3GraphVertex& vertex : m_graphp->vertices()) { vertex.color(0); vertex.user(0); } // Color graph for (V3GraphVertex& vertex : m_graphp->vertices()) { if (!vertex.user()) { m_currentDfs++; vertexIterate(&vertex); } } // If there's a single vertex of a color, it doesn't need a subgraph // This simplifies the consumer's code, and reduces graph debugging clutter for (V3GraphVertex& vertex : m_graphp->vertices()) { bool onecolor = true; for (V3GraphEdge& edge : vertex.outEdges()) { if (followEdge(&edge)) { if (vertex.color() == edge.top()->color()) { onecolor = false; break; } } } if (onecolor) vertex.color(0); } } void vertexIterate(V3GraphVertex* vertexp) { const uint32_t thisDfsNum = m_currentDfs++; vertexp->user(thisDfsNum); vertexp->color(0); for (V3GraphEdge& edge : vertexp->outEdges()) { if (followEdge(&edge)) { V3GraphVertex* top = edge.top(); if (!top->user()) { // Dest not computed yet vertexIterate(top); } if (!top->color()) { // Dest not in a component if (vertexp->user() > top->user()) vertexp->user(top->user()); } } } if (vertexp->user() == thisDfsNum) { // New head of subtree vertexp->color(thisDfsNum); // Mark as component while (!m_callTrace.empty()) { V3GraphVertex* popVertexp = m_callTrace.back(); if (popVertexp->user() >= thisDfsNum) { // Lower node is part of this subtree m_callTrace.pop_back(); popVertexp->color(thisDfsNum); } else { break; } } } else { // In another subtree (maybe...) m_callTrace.push_back(vertexp); } } public: GraphAlgStrongly(V3Graph* graphp, V3EdgeFuncP edgeFuncp) : GraphAlg<>{graphp, edgeFuncp} { main(); } ~GraphAlgStrongly() = default; }; void V3Graph::stronglyConnected(V3EdgeFuncP edgeFuncp) { GraphAlgStrongly{this, edgeFuncp}; } //###################################################################### //###################################################################### // Algorithms - ranking // Changes user() and rank() class GraphAlgRank final : GraphAlg<> { void main() { // Rank each vertex, ignoring cutable edges // Vertex::m_user begin: 1 indicates processing, 2 indicates completed // Clear existing ranks for (V3GraphVertex& vertex : m_graphp->vertices()) { vertex.rank(0); vertex.user(0); } for (V3GraphVertex& vertex : m_graphp->vertices()) { if (!vertex.user()) { // vertexIterate(&vertex, 1); } } } void vertexIterate(V3GraphVertex* vertexp, uint32_t currentRank) { // Assign rank to each unvisited node // If larger rank is found, assign it and loop back through // If we hit a back node make a list of all loops if (vertexp->user() == 1) { m_graphp->reportLoops(m_edgeFuncp, vertexp); m_graphp->loopsMessageCb(vertexp); return; // LCOV_EXCL_LINE // gcc gprof bug misses this return } if (vertexp->rank() >= currentRank) return; // Already processed it vertexp->user(1); vertexp->rank(currentRank); for (V3GraphEdge& edge : vertexp->outEdges()) { if (followEdge(&edge)) vertexIterate(edge.top(), currentRank + vertexp->rankAdder()); } vertexp->user(2); } public: GraphAlgRank(V3Graph* graphp, V3EdgeFuncP edgeFuncp) : GraphAlg<>{graphp, edgeFuncp} { main(); } ~GraphAlgRank() = default; }; void V3Graph::rank() { GraphAlgRank{this, &V3GraphEdge::followAlwaysTrue}; } void V3Graph::rank(V3EdgeFuncP edgeFuncp) { GraphAlgRank{this, edgeFuncp}; } //###################################################################### //###################################################################### // Algorithms - report loops // Changes user() class GraphAlgRLoops final : GraphAlg<> { std::vector m_callTrace; // List of everything we hit processing so far bool m_done = false; // Exit algorithm void main(V3GraphVertex* vertexp) { // Vertex::m_user begin: 1 indicates processing, 2 indicates completed // Clear existing ranks m_graphp->userClearVertices(); m_callTrace.reserve(100); vertexIterate(vertexp, 0); } void vertexIterate(V3GraphVertex* vertexp, uint32_t currentRank) { // Assign rank to each unvisited node // When we hit ourself again, return the list of all loops if (m_done) return; // Can't just reserve(), unless we modify size() before setting array directly while (m_callTrace.size() <= currentRank) m_callTrace.push_back(vertexp); m_callTrace[currentRank++] = vertexp; if (vertexp->user() == 1) { for (unsigned i = 0; i < currentRank; i++) { // m_graphp->loopsVertexCb(m_callTrace[i]); } m_done = true; return; } if (vertexp->user() == 2) return; // Already processed it vertexp->user(1); for (V3GraphEdge& edge : vertexp->outEdges()) { if (followEdge(&edge)) vertexIterate(edge.top(), currentRank); } vertexp->user(2); } public: GraphAlgRLoops(V3Graph* graphp, V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp) : GraphAlg<>{graphp, edgeFuncp} { main(vertexp); } ~GraphAlgRLoops() = default; }; void V3Graph::reportLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp) { GraphAlgRLoops{this, edgeFuncp, vertexp}; } //###################################################################### //###################################################################### // Algorithms - subtrees // Changes user() class GraphAlgSubtrees final : GraphAlg<> { V3Graph* const m_loopGraphp; //! Iterate through all connected nodes of a graph with a loop or loops. V3GraphVertex* vertexIterateAll(V3GraphVertex* vertexp) { if (V3GraphVertex* newVertexp = static_cast(vertexp->userp())) { return newVertexp; } else { newVertexp = vertexp->clone(m_loopGraphp); vertexp->userp(newVertexp); for (V3GraphEdge& edge : vertexp->outEdges()) { if (followEdge(&edge)) { V3GraphEdge* newEdgep = static_cast(edge.userp()); if (!newEdgep) { V3GraphVertex* newTop = vertexIterateAll(edge.top()); newEdgep = edge.clone(m_loopGraphp, newVertexp, newTop); edge.userp(newEdgep); } } } return newVertexp; } } public: GraphAlgSubtrees(V3Graph* graphp, V3Graph* loopGraphp, V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp) : GraphAlg<>{graphp, edgeFuncp} , m_loopGraphp{loopGraphp} { // Vertex::m_userp - New vertex if we have seen this vertex already // Edge::m_userp - New edge if we have seen this edge already m_graphp->userClearVertices(); m_graphp->userClearEdges(); (void)vertexIterateAll(vertexp); } ~GraphAlgSubtrees() = default; }; //! Report the entire connected graph with a loop or loops void V3Graph::subtreeLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp, V3Graph* loopGraphp) { GraphAlgSubtrees{this, loopGraphp, edgeFuncp, vertexp}; } //###################################################################### //###################################################################### // Algorithms - sorting struct GraphSortVertexCmp final { bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { return lhsp->sortCmp(rhsp) < 0; } }; struct GraphSortEdgeCmp final { bool operator()(const V3GraphEdge* lhsp, const V3GraphEdge* rhsp) const { return lhsp->sortCmp(rhsp) < 0; } }; void V3Graph::sortVertices() { // Sort list of vertices by rank, then fanout std::vector vertices; for (V3GraphVertex& vertex : m_vertices) vertices.push_back(&vertex); std::stable_sort(vertices.begin(), vertices.end(), GraphSortVertexCmp()); // Re-insert in sorted order for (V3GraphVertex* const ip : vertices) { m_vertices.unlink(ip); m_vertices.linkBack(ip); } } void V3Graph::sortEdges() { // Sort edges by rank then fanout of node they point to std::vector edges; for (V3GraphVertex& vertex : vertices()) { // Make a vector for (V3GraphEdge& edge : vertex.outEdges()) edges.push_back(&edge); // Sort std::stable_sort(edges.begin(), edges.end(), GraphSortEdgeCmp()); // Relink edges in specified order for (V3GraphEdge* const edgep : edges) edgep->relinkFromp(&vertex); // Prep for next edges.clear(); } } //###################################################################### //###################################################################### // Algorithms - ordering // Compute near optimal ordering of the nodes, where: // If a required edge is A->B, rank(A)user() == 2) return vertexp->fanout(); // Already processed it UASSERT_OBJ(vertexp->user() != 1, vertexp, "Loop found, backward edges should be dead"); vertexp->user(1); double fanout = 0; for (V3GraphEdge& edge : vertexp->outEdges()) { if (edge.weight()) fanout += orderDFSIterate(edge.top()); } // Just count inbound edges for (V3GraphEdge& edge : vertexp->inEdges()) { if (edge.weight()) ++fanout; } vertexp->fanout(fanout); vertexp->user(2); return vertexp->fanout(); } //###################################################################### //###################################################################### // Algorithms - parallelism report class GraphAlgParallelismReport final { // MEMBERS V3Graph& m_graph; // The graph const std::function m_vertexCost; // vertex cost function V3Graph::ParallelismReport m_report; // The result report // CONSTRUCTORS explicit GraphAlgParallelismReport(V3Graph& graph, std::function vertexCost) : m_graph{graph} , m_vertexCost{vertexCost} { // For each node, record the critical path cost from the start // of the graph through the end of the node. std::unordered_map critPaths; GraphStreamUnordered serialize{&m_graph}; for (const V3GraphVertex* vertexp; (vertexp = serialize.nextp());) { ++m_report.m_vertexCount; uint64_t cpCostToHere = 0; for (const V3GraphEdge& edge : vertexp->inEdges()) { ++m_report.m_edgeCount; // For each upstream item, add its critical path cost to // the cost of this edge, to form a new candidate critical // path cost to the current node. Whichever is largest is // the critical path to reach the start of this node. cpCostToHere = std::max(cpCostToHere, critPaths[edge.fromp()]); } // Include the cost of the current vertex in the critical // path, so it represents the critical path to the end of // this vertex. cpCostToHere += m_vertexCost(vertexp); critPaths[vertexp] = cpCostToHere; m_report.m_criticalPathCost = std::max(m_report.m_criticalPathCost, cpCostToHere); // Tally the total cost contributed by vertices. m_report.m_totalGraphCost += m_vertexCost(vertexp); } } ~GraphAlgParallelismReport() = default; VL_UNCOPYABLE(GraphAlgParallelismReport); VL_UNMOVABLE(GraphAlgParallelismReport); public: static V3Graph::ParallelismReport apply(V3Graph& graph, std::function vertexCost) { return GraphAlgParallelismReport(graph, vertexCost).m_report; } }; V3Graph::ParallelismReport V3Graph::parallelismReport(std::function vertexCost) { return GraphAlgParallelismReport::apply(*this, vertexCost); }