diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0d44b037..cd985af00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -230,6 +230,7 @@ set(COMMON_SOURCES V3DfgAstToDfg.cpp V3DfgBreakCycles.cpp V3DfgCache.cpp + V3DfgColorSCCs.cpp V3DfgDecomposition.cpp V3DfgDfgToAst.cpp V3DfgOptimizer.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index ee5cb7617..cdec03d91 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -242,6 +242,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgAstToDfg.o \ V3DfgBreakCycles.o \ V3DfgCache.o \ + V3DfgColorSCCs.o \ V3DfgDecomposition.o \ V3DfgDfgToAst.o \ V3DfgOptimizer.o \ diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 1a009d586..533d76030 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -28,135 +28,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -// 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::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 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(); - // 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().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([&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(); - // 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() { - // 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. - - // 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(); - // 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(); - // 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 size_t component = vtx.getUser().component; - vtx.setUser(component); - }); - } - -public: - // Sets DfgVertex::user() 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; - } -}; - class TraceDriver final : public DfgVisitor { // TYPES @@ -191,7 +62,7 @@ class TraceDriver final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The graph being processed // The strongly connected component we are trying to escape - const uint32_t m_component; + const uint64_t m_component; const bool m_aggressive; // Trace aggressively, creating intermediate ops uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex uint32_t m_msb = 0; // MSB to extract from the currently visited Vertex @@ -211,7 +82,7 @@ class TraceDriver final : public DfgVisitor { // taken from 'refp', but 'refp' is otherwise not used. You should // always use this to create new vertices, so unused ones (if a trace // eventually fails) can be cleaned up at the end. This also sets the - // vertex user to 0, indicating the new vertex is not part of a + // vertex user to 0, indicating the new vertex is not part of a // strongly connected component. This should always be true, as all the // vertices we create here are driven from outside the component we are // trying to escape, and will sink into that component. Given those are @@ -234,7 +105,7 @@ class TraceDriver final : public DfgVisitor { if VL_CONSTEXPR_CXX17 (std::is_same::value) { DfgConst* const vtxp = new DfgConst{m_dfg, refp->fileline(), width}; - vtxp->template setUser(0); + vtxp->template setUser(0); m_newVtxps.emplace_back(vtxp); return reinterpret_cast(vtxp); } else { @@ -245,7 +116,7 @@ class TraceDriver final : public DfgVisitor { Vertex>::type; AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width); Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), dtypep}; - vtxp->template setUser(0); + vtxp->template setUser(0); m_newVtxps.emplace_back(vtxp); return reinterpret_cast(vtxp); } @@ -283,7 +154,7 @@ class TraceDriver final : public DfgVisitor { // Trace the vertex onStackr = true; - if (vtxp->getUser() != m_component) { + if (vtxp->getUser() != m_component) { // If the currently traced vertex is in a different component, // then we found what we were looking for. if (msb != vtxp->width() - 1 || lsb != 0) { @@ -564,7 +435,7 @@ class TraceDriver final : public DfgVisitor { #undef SET_RESULT // CONSTRUCTOR - TraceDriver(DfgGraph& dfg, uint32_t component, bool aggressive) + TraceDriver(DfgGraph& dfg, uint64_t component, bool aggressive) : m_dfg{dfg} , m_component{component} , m_aggressive{aggressive} { @@ -577,7 +448,7 @@ class TraceDriver final : public DfgVisitor { } public: - // Given a Vertex that is part of an SCC denoted by vtxp->user(), + // Given a Vertex that is part of an SCC denoted by vtxp->user(), // return a vertex that is equivalent to 'vtxp[lsb +: width]', but is not // part of the same SCC. Returns nullptr if such a vertex cannot be // computed. This can add new vertices to the graph. The 'aggressive' flag @@ -586,7 +457,7 @@ public: // waste a lot of compute. static DfgVertex* apply(DfgGraph& dfg, DfgVertex* vtxp, uint32_t lsb, uint32_t width, bool aggressive) { - TraceDriver traceDriver{dfg, vtxp->getUser(), aggressive}; + TraceDriver traceDriver{dfg, vtxp->getUser(), aggressive}; // Find the out-of-component driver of the given vertex DfgVertex* const resultp = traceDriver.trace(vtxp, lsb + width - 1, lsb); // Delete unused newly created vertices (these can be created if a @@ -608,7 +479,7 @@ public: class IndependentBits final : public DfgVisitor { // STATE - const uint32_t m_component; // The component the start vertex is part of + const uint64_t m_component; // The component the start vertex is part of // Vertex to current bit mask map. The mask is set for the bits that **depend** on 'm_varp'. std::unordered_map m_vtxp2Mask; @@ -623,7 +494,7 @@ class IndependentBits final : public DfgVisitor { std::forward_as_tuple(vtxp), // std::forward_as_tuple(vtxp->fileline(), static_cast(vtxp->width()), 0)); // Initialize to all ones if the vertex is part of the same component, otherwise zeroes - if (pair.second && vtxp->getUser() == m_component) { + if (pair.second && vtxp->getUser() == m_component) { pair.first->second.setAllBits1(); } return pair.first->second; @@ -779,7 +650,7 @@ class IndependentBits final : public DfgVisitor { // CONSTRUCTOR IndependentBits(DfgGraph& dfg, DfgVertex* vtxp) - : m_component{vtxp->getUser()} { + : m_component{vtxp->getUser()} { if (v3Global.opt.debugCheck()) { m_lineCoverageFile.open( // v3Global.opt.makeDir() + "/" + v3Global.opt.prefix() @@ -792,7 +663,7 @@ class IndependentBits final : public DfgVisitor { // Enqueue every operation vertex in the analysed component for (DfgVertex& vtx : dfg.opVertices()) { - if (vtx.getUser() == m_component) workList.emplace_back(&vtx); + if (vtx.getUser() == m_component) workList.emplace_back(&vtx); } // While there is an item on the worklist ... @@ -833,7 +704,7 @@ class IndependentBits final : public DfgVisitor { } public: - // Given a Vertex that is part of an SCC denoted by vtxp->user(), + // Given a Vertex that is part of an SCC denoted by vtxp->user(), // compute which bits of this vertex have a value that is independent of // the current value of the Vertex itself (simple forward dataflow // analysis). Returns a bit mask where a set bit indicates that bit is @@ -852,10 +723,10 @@ public: class FixUpSelDrivers final { static size_t fixUpSelSinks(DfgGraph& dfg, DfgVertex* vtxp) { size_t nImprovements = 0; - const uint32_t component = vtxp->getUser(); + const uint64_t component = vtxp->getUser(); vtxp->forEachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (sink.getUser() != component) return; + if (sink.getUser() != component) return; // Only handle Sel DfgSel* const selp = sink.cast(); if (!selp) return; @@ -873,10 +744,10 @@ class FixUpSelDrivers final { static size_t fixUpArraySelSinks(DfgGraph& dfg, DfgVertex* vtxp) { size_t nImprovements = 0; - const uint32_t component = vtxp->getUser(); + const uint64_t component = vtxp->getUser(); vtxp->forEachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (sink.getUser() != component) return; + if (sink.getUser() != component) return; // Only handle ArraySels DfgArraySel* const aselp = sink.cast(); if (!aselp) return; @@ -976,7 +847,7 @@ class FixUpIndependentRanges final { AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width); DfgSel* const selp = new DfgSel{dfg, vtxp->fileline(), dtypep}; // Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp' - selp->setUser(vtxp->getUser()); + selp->setUser(vtxp->getUser()); // Do not connect selp->fromp yet, need to do afer replacing 'vtxp' selp->lsb(lsb); termp = selp; @@ -1032,7 +903,7 @@ class FixUpIndependentRanges final { } else { // The range is not used, just use constant 0 as a placeholder DfgConst* const constp = new DfgConst{dfg, flp, msb - lsb + 1}; - constp->setUser(0); + constp->setUser(0); termps.emplace_back(constp); } // Next iteration @@ -1044,7 +915,7 @@ class FixUpIndependentRanges final { if (nImprovements) { // Concatenate all the terms to create the replacement DfgVertex* replacementp = termps.front(); - const uint32_t vComp = vtxp->getUser(); + const uint64_t vComp = vtxp->getUser(); for (size_t i = 1; i < termps.size(); ++i) { DfgVertex* const termp = termps[i]; const uint32_t catWidth = replacementp->width() + termp->width(); @@ -1057,9 +928,9 @@ class FixUpIndependentRanges final { // component, it's part of that component, otherwise its not // cyclic (all terms are from outside the original component, // and feed into the original component). - const uint32_t tComp = termp->getUser(); - const uint32_t rComp = replacementp->getUser(); - catp->setUser(tComp == vComp || rComp == vComp ? vComp : 0); + const uint64_t tComp = termp->getUser(); + const uint64_t rComp = replacementp->getUser(); + catp->setUser(tComp == vComp || rComp == vComp ? vComp : 0); replacementp = catp; } @@ -1089,10 +960,10 @@ public: nImprovements += fixUpPacked(dfg, varp); } else if (varp->is()) { // For array variables, fix up element-wise - const uint32_t component = varp->getUser(); + const uint64_t component = varp->getUser(); varp->forEachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (sink.getUser() != component) return; + if (sink.getUser() != component) return; // Only handle ArraySels with constant index DfgArraySel* const aselp = sink.cast(); if (!aselp) return; @@ -1141,9 +1012,9 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Iterate while an improvement can be made and the graph is still cyclic do { - // Color SCCs (populates DfgVertex::user()) + // Color SCCs (populates DfgVertex::user()) const auto userDataInUse = res.userDataInUse(); - const uint32_t numNonTrivialSCCs = ColorStronglyConnectedComponents::apply(res); + const uint32_t numNonTrivialSCCs = V3DfgPasses::colorStronglyConnectedComponents(res); // Congrats if it has become acyclic if (!numNonTrivialSCCs) { @@ -1160,7 +1031,7 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Method 1: FixUpSelDrivers for (DfgVertexVar& vtx : res.varVertices()) { // If Variable is not part of a cycle, move on - const uint32_t component = vtx.getUser(); + const uint64_t component = vtx.getUser(); if (!component) continue; const size_t nFixed = FixUpSelDrivers::apply(res, &vtx); @@ -1177,7 +1048,7 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Method 2. FixUpIndependentRanges for (DfgVertexVar& vtx : res.varVertices()) { // If Variable is not part of a cycle, move on - const uint32_t component = vtx.getUser(); + const uint64_t component = vtx.getUser(); if (!component) continue; const size_t nFixed = FixUpIndependentRanges::apply(res, &vtx); diff --git a/src/V3DfgColorSCCs.cpp b/src/V3DfgColorSCCs.cpp new file mode 100644 index 000000000..95da70dbc --- /dev/null +++ b/src/V3DfgColorSCCs.cpp @@ -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 +#include + +// 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::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 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(); + // 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().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([&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(); + // 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(); + // 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(); + // 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().component; + vtx.setUser(component); + }); + } + +public: + // Sets DfgVertex::user() 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); +} diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp index 3bb4d4581..214dd13c8 100644 --- a/src/V3DfgDecomposition.cpp +++ b/src/V3DfgDecomposition.cpp @@ -21,6 +21,7 @@ #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Dfg.h" +#include "V3DfgPasses.h" #include "V3File.h" #include @@ -117,173 +118,42 @@ std::vector> DfgGraph::splitIntoComponents(std::string } class ExtractCyclicComponents final { - static constexpr size_t UNASSIGNED = std::numeric_limits::max(); - // TYPES - struct VertexState final { - size_t index = UNASSIGNED; // Used by Pearce's algorithm for detecting SCCs - size_t component = UNASSIGNED; // Result component number (0 stays in input graph) - bool merged = false; // Visited in the merging pass - VertexState(){}; + // We reuse the DfgVertex::user state set by V3DfgPasses::colorStronglyConnectedComponents. + // We sneak an extra flag into the MSB to indicate the vertex was merged already. + class VertexState final { + uint64_t& m_userr; + + public: + VertexState(DfgVertex& vtx) + : m_userr{vtx.getUser()} {} + bool merged() const { return m_userr >> 63; } + void setMerged() { m_userr |= 1ULL << 63; } + uint64_t component() const { return m_userr & ~(1ULL << 63); } + void component(uint64_t value) { m_userr = (m_userr & (1ULL << 63)) | value; } }; // STATE - - //========================================================================== - // Shared state - DfgGraph& m_dfg; // The input graph - std::deque m_stateStorage; // Container for VertexState instances const std::string m_prefix; // Component name prefix - size_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph const bool m_doExpensiveChecks = v3Global.opt.debugCheck(); - - //========================================================================== - // State for Pearce's algorithm for detecting SCCs - - size_t m_index = 0; // Visitation index counter - std::vector m_stack; // The stack used by the algorithm - - //========================================================================== - // State for extraction - // The extracted cyclic components std::vector> m_components; // Map from 'variable vertex' -> 'component index' -> 'clone in that component' - std::unordered_map> m_clones; + std::unordered_map> m_clones; // METHODS - - //========================================================================== - // Shared methods - - VertexState& state(DfgVertex& vtx) const { return *vtx.getUser(); } - - VertexState& allocState(DfgVertex& vtx) { - VertexState*& statep = vtx.user(); - UASSERT_OBJ(!statep, &vtx, "Vertex state already allocated " << cvtToHex(statep)); - m_stateStorage.emplace_back(); - statep = &m_stateStorage.back(); - return *statep; - } - - VertexState& getOrAllocState(DfgVertex& vtx) { - VertexState*& statep = vtx.user(); - if (!statep) { - m_stateStorage.emplace_back(); - statep = &m_stateStorage.back(); - } - return *statep; - } - - //========================================================================== - // Methods for Pearce's algorithm to detect strongly connected components - - 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 = getOrAllocState(child); - // 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() || state(*m_stack.back()).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([&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 = state(*m_stack.back()); - // 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() { - // 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. - - // We can leverage some properties of the input graph to gain a bit of speed. Firstly, we - // know constant nodes have no in edges, so they cannot be part of a non-trivial SCC. Mark - // them as such without starting a whole traversal. - for (DfgConst& vtx : m_dfg.constVertices()) { - VertexState& vtxState = allocState(vtx); - vtxState.index = 0; - vtxState.component = 0; - } - - // Next, we know that all SCCs must include a variable (as the input graph was converted - // from an AST, we can only have a cycle by going through a variable), so we only start - // traversals through them, and only if we know they have both in and out edges. - for (DfgVertexVar& vtx : m_dfg.varVertices()) { - if (vtx.arity() > 0 && vtx.hasSinks()) { - VertexState& vtxState = getOrAllocState(vtx); - // If not yet visited, start a traversal - if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState); - } else { - VertexState& vtxState = getOrAllocState(vtx); - 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; - } - } - - // Finally, everything we did not visit through the traversal of a variable cannot be in an - // SCC, (otherwise we would have found it from a variable). - for (DfgVertex& vtx : m_dfg.opVertices()) { - VertexState& vtxState = getOrAllocState(vtx); - if (vtxState.index == UNASSIGNED) { - vtxState.index = 0; - vtxState.component = 0; - } - } - } - - //========================================================================== - // Methods for merging - - void visitMergeSCCs(DfgVertex& vtx, size_t targetComponent) { - VertexState& vtxState = state(vtx); + void visitMergeSCCs(DfgVertex& vtx, uint64_t targetComponent) { + VertexState vtxState{vtx}; // Move on if already visited - if (vtxState.merged) return; + if (vtxState.merged()) return; // Visiting vertex - vtxState.merged = true; + vtxState.setMerged(); // Assign vertex to the target component - vtxState.component = targetComponent; + vtxState.component(targetComponent); // Visit all neighbors. We stop at variable boundaries, // which is where we will split the graphs @@ -304,16 +174,14 @@ class ExtractCyclicComponents final { for (DfgVertex& vtx : m_dfg.opVertices()) { // Start DFS from each vertex that is in a non-trivial SCC, and merge everything // that is reachable from it into this component. - if (const size_t target = state(vtx).component) visitMergeSCCs(vtx, target); + if (const uint64_t target = VertexState{vtx}.component()) visitMergeSCCs(vtx, target); } } - //========================================================================== - // Methods for extraction - // Retrieve clone of vertex in the given component - DfgVertexVar& getClone(DfgVertexVar& vtx, size_t component) { - UASSERT_OBJ(state(vtx).component != component, &vtx, "Vertex is in that component"); + DfgVertexVar& getClone(DfgVertexVar& vtx, uint64_t component) { + UASSERT_OBJ(VertexState{vtx}.component() != component, &vtx, + "Vertex is in that component"); DfgVertexVar*& clonep = m_clones[&vtx][component]; if (!clonep) { if (DfgVarPacked* const pVtxp = vtx.cast()) { @@ -330,21 +198,20 @@ class ExtractCyclicComponents final { } } UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type"); - VertexState& cloneStatep = allocState(*clonep); - cloneStatep.component = component; + clonep->setUser(component); } return *clonep; } // Fix edges that cross components void fixEdges(DfgVertexVar& vtx) { - const size_t component = state(vtx).component; + const uint64_t component = VertexState{vtx}.component(); // Fix up sources in a different component vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) { DfgVertex* const srcp = edge.sourcep(); if (!srcp) return; - const size_t sourceComponent = state(*srcp).component; + const uint64_t sourceComponent = VertexState{*srcp}.component(); // Same component is OK if (sourceComponent == component) return; // Relink the source to write the clone @@ -354,7 +221,7 @@ class ExtractCyclicComponents final { // Fix up sinks in a different component vtx.forEachSinkEdge([&](DfgEdge& edge) { - const size_t sinkComponent = state(*edge.sinkp()).component; + const uint64_t sinkComponent = VertexState{*edge.sinkp()}.component(); // Same component is OK if (sinkComponent == component) return; // Relink the sink to read the clone @@ -366,7 +233,7 @@ class ExtractCyclicComponents final { void moveVertices(DfgVertex::List& list) { for (DfgVertex* const vtxp : list.unlinkable()) { DfgVertex& vtx = *vtxp; - if (const size_t component = state(vtx).component) { + if (const uint64_t component = VertexState{vtx}.component()) { m_dfg.removeVertex(vtx); m_components[component - 1]->addVertex(vtx); } @@ -378,15 +245,15 @@ class ExtractCyclicComponents final { // - Edges only cross components at variable boundaries // - Variable vertex sources are all connected. dfg.forEachVertex([&](DfgVertex& vtx) { - const size_t component = state(vtx).component; + const uint64_t component = VertexState{vtx}.component(); vtx.forEachSource([&](DfgVertex& src) { if (src.is()) return; // OK to cross at variables - UASSERT_OBJ(component == state(src).component, &vtx, + UASSERT_OBJ(component == VertexState{src}.component(), &vtx, "Edge crossing components without variable involvement"); }); vtx.forEachSink([&](DfgVertex& snk) { if (snk.is()) return; // OK to cross at variables - UASSERT_OBJ(component == state(snk).component, &vtx, + UASSERT_OBJ(component == VertexState{snk}.component(), &vtx, "Edge crossing components without variable involvement"); }); }); @@ -408,10 +275,10 @@ class ExtractCyclicComponents final { }); } - void extractComponents() { + void extractComponents(uint32_t numNonTrivialSCCs) { // Allocate result graphs - m_components.resize(m_nonTrivialSCCs); - for (size_t i = 0; i < m_nonTrivialSCCs; ++i) { + m_components.resize(numNonTrivialSCCs); + for (uint32_t i = 0; i < numNonTrivialSCCs; ++i) { m_components[i].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i)}); } @@ -450,17 +317,17 @@ class ExtractCyclicComponents final { explicit ExtractCyclicComponents(DfgGraph& dfg, const std::string& label) : m_dfg{dfg} , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} { - // VertexState is stored as user data + // DfgVertex::user is set to the SCC number by colorStronglyConnectedComponents, + // Then we use VertexState to handle the MSB as an extra flag. const auto userDataInUse = dfg.userDataInUse(); // Find all the non-trivial SCCs (and trivial cycles) in the graph - colorSCCs(); - // If the graph was acyclic (which should be the common case), - // there will be no non-trivial SCCs, so we are done. - if (!m_nonTrivialSCCs) return; + const uint32_t numNonTrivialSCCs = V3DfgPasses::colorStronglyConnectedComponents(dfg); + // If the graph was acyclic (which should be the common case), then we are done. + if (!numNonTrivialSCCs) return; // Ensure that component boundaries are always at variables, by merging SCCs mergeSCCs(); // Extract the components - extractComponents(); + extractComponents(numNonTrivialSCCs); } public: diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index b17fa1002..21311ae8f 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -60,6 +60,14 @@ void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Construct binary to oneHot decoders void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED; +// Sets DfgVertex::user() 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) +uint32_t colorStronglyConnectedComponents(DfgGraph&) VL_MT_DISABLED; // Common subexpression elimination void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED; // Inline fully driven variables