From fb66174d8042c35d6c63d84b8169b919c5a26a56 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 9 Apr 2026 12:32:57 +0100 Subject: [PATCH] Internals: Run Dfg passes on all components at once This is just a reordering of pass applications to make --stats more useful. No functional change. --- src/V3DfgContext.h | 17 +--------- src/V3DfgOptimizer.cpp | 74 ++++++++++++++++++++++++++++++----------- src/V3DfgPasses.cpp | 30 ----------------- src/V3DfgPasses.h | 48 +++++++++++--------------- src/V3DfgPatternStats.h | 46 +++++++++++++++---------- 5 files changed, 104 insertions(+), 111 deletions(-) diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index be223f494..46de10ccc 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -401,24 +401,9 @@ public: V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; - // Node pattern collector - V3DfgPatternStats m_patternStats; - // CONSTRUCTOR V3DfgContext() = default; - - ~V3DfgContext() { - // Print the collected patterns - if (v3Global.opt.stats()) { - // File to dump to - const std::string filename = v3Global.opt.hierTopDataDir() + "/" - + v3Global.opt.prefix() + "__stats_dfg_patterns.txt"; - // Open, write, close - const std::unique_ptr ofp{V3File::new_ofstream(filename)}; - if (ofp->fail()) v3fatal("Can't write file: " << filename); - m_patternStats.dump(*ofp); - } - } + ~V3DfgContext() = default; }; #endif //VERILATOR_V3DFGCONTEXT_H_ diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 638386f56..e32a64be9 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -257,13 +257,30 @@ class DataflowOptimize final { // STATE V3DfgContext m_ctx; // The context holding values that need to persist across multiple graphs - void endOfStage(const std::string& name, const DfgGraph* dfgp = nullptr) { - // Dump the graph for debugging if given one - if (VL_UNLIKELY(dumpDfgLevel() >= 8 && dfgp)) dfgp->dumpDotFilePrefixed(name); - // Dump stage stats only in scoped mode when running on the whole netlist + void endOfStage(const std::string& name) { if (VL_UNLIKELY(v3Global.opt.stats())) V3Stats::statsStage("dfg-optimize-" + name); } + void endOfStage(const std::string& name, const DfgGraph& dfg, + const std::vector>& componentps) { + // Dump the graphs for debugging + if (VL_UNLIKELY(dumpDfgLevel() >= 5)) { + if (dfg.size() > 0) dfg.dumpDotFilePrefixed(name); + for (const std::unique_ptr& componentp : componentps) { + if (componentp->size() > 0) componentp->dumpDotFilePrefixed(name); + } + } + // Type check the graphs + if (VL_UNLIKELY(v3Global.opt.debugCheck())) { + V3DfgPasses::typeCheck(dfg); + for (const std::unique_ptr& componentp : componentps) { + V3DfgPasses::typeCheck(*componentp); + } + } + // Dump stage stats + endOfStage(name); + } + // Mark variables with external references void markExternallyReferencedVariables(AstNetlist* netlistp) { netlistp->foreach([](AstNode* nodep) { @@ -297,17 +314,17 @@ class DataflowOptimize final { void optimize(DfgGraph& dfg) { // Remove unobservable variabels and logic that drives only such variables V3DfgPasses::removeUnobservable(dfg, m_ctx); - endOfStage("remove-unobservable", &dfg); + endOfStage("removeUnobservable", dfg, {}); // Synthesize DfgLogic vertices V3DfgPasses::synthesize(dfg, m_ctx); - endOfStage("synthesize", &dfg); + endOfStage("synthesize", dfg, {}); // Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a // DAG, and large, mostly acyclic graphs could not be optimized due to the presence of // small cycles. std::vector> cyclicComps = dfg.extractCyclicComponents("cyclic"); - endOfStage("extract-cyclic"); + endOfStage("extractCyclic", dfg, cyclicComps); // Attempt to convert cyclic components into acyclic ones std::vector> madeAcyclicComponents; @@ -330,27 +347,46 @@ class DataflowOptimize final { } // Merge those that were made acyclic back to the graph, this enables optimizing more dfg.mergeGraphs(std::move(madeAcyclicComponents)); - endOfStage("break-cycles"); + endOfStage("breakCycles", dfg, cyclicComps); // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); - endOfStage("split-acyclic"); + endOfStage("splitAcyclic", dfg, acyclicComps); // Optimize each acyclic component - for (const std::unique_ptr& component : acyclicComps) { - V3DfgPasses::optimize(*component, m_ctx); + for (auto& cp : acyclicComps) V3DfgPasses::inlineVars(*cp); + endOfStage("inlineVars", dfg, acyclicComps); + for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext0); + endOfStage("cse0", dfg, acyclicComps); + for (auto& cp : acyclicComps) V3DfgPasses::binToOneHot(*cp, m_ctx.m_binToOneHotContext); + endOfStage("binToOneHot", dfg, acyclicComps); + for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext); + endOfStage("peephole", dfg, acyclicComps); + for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext); + endOfStage("pushDownSels", dfg, acyclicComps); + for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext1); + endOfStage("cse1", dfg, acyclicComps); + + // Accumulate patterns for reporting + if (v3Global.opt.stats()) { + { + V3DfgPatternStats patternStats; + for (auto& cp : acyclicComps) patternStats.accumulate(*cp); + } + endOfStage("patterns"); } + // Merge everything back under the main DFG dfg.mergeGraphs(std::move(acyclicComps)); dfg.mergeGraphs(std::move(cyclicComps)); - endOfStage("optimize", &dfg); + endOfStage("optimized", dfg, {}); // Regularize the graph after merging it all back together so all // references are known and we only need to iterate the Ast once // to replace redundant variables. V3DfgPasses::regularize(dfg, m_ctx.m_regularizeContext); - endOfStage("regularize", &dfg); + endOfStage("regularize", dfg, {}); } void removeNeverActives(AstNetlist* netlistp) { @@ -372,7 +408,6 @@ class DataflowOptimize final { } DataflowOptimize(AstNetlist* netlistp) { - // Mark interfaces that might be referenced by a virtual interface if (v3Global.hasVirtIfaces()) { netlistp->typeTablep()->foreach([](const AstIfaceRefDType* nodep) { @@ -380,25 +415,26 @@ class DataflowOptimize final { nodep->ifaceViaCellp()->setHasVirtualRef(); }); } - // Mark variables with external references markExternallyReferencedVariables(netlistp); - + // Dump stage stats + endOfStage("init"); // Post V3Scope application. Run on whole netlist. UINFO(4, "Applying DFG optimization to entire netlist"); // Build the DFG of the entire netlist const std::unique_ptr dfgp = V3DfgPasses::astToDfg(*netlistp, m_ctx); - endOfStage("ast-to-dfg", dfgp.get()); + endOfStage("astToDfg", *dfgp, {}); // Actually process the graph optimize(*dfgp); // Convert back to Ast V3DfgPasses::dfgToAst(*dfgp, m_ctx); - endOfStage("dfg-to-ast", dfgp.get()); + endOfStage("dfgToAst", *dfgp, {}); // Some sentrees might have become constant, remove them removeNeverActives(netlistp); - // Reset interned types so the corresponding Ast types can be garbage collected DfgDataType::reset(); + // Dump stage stats + endOfStage("fini"); } public: diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index fdf791829..eaad8872e 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -466,33 +466,3 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { } } } - -void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgContext& ctx) { - // There is absolutely nothing useful we can do with a graph of size 2 or less - if (dfg.size() <= 2) return; - - const auto run = [&](const std::string& name, bool dump, std::function pass) { - // Apply the pass - pass(); - // Debug dump - if (dump) dfg.dumpDotFilePrefixed("opt-" + VString::removeWhitespace(name)); - // Internal type check - if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg); - }; - - // Currend debug dump level - const uint32_t dumpLvl = dumpDfgLevel(); - - // Run passes - run("input ", dumpLvl >= 3, [&]() { /* debug dump only */ }); - run("inlineVars ", dumpLvl >= 4, [&]() { inlineVars(dfg); }); - run("cse0 ", dumpLvl >= 4, [&]() { cse(dfg, ctx.m_cseContext0); }); - run("binToOneHot ", dumpLvl >= 4, [&]() { binToOneHot(dfg, ctx.m_binToOneHotContext); }); - run("peephole ", dumpLvl >= 4, [&]() { peephole(dfg, ctx.m_peepholeContext); }); - run("pushDownSels", dumpLvl >= 4, [&]() { pushDownSels(dfg, ctx.m_pushDownSelsContext); }); - run("cse1 ", dumpLvl >= 4, [&]() { cse(dfg, ctx.m_cseContext1); }); - run("output ", dumpLvl >= 3, [&]() { /* debug dump only */ }); - - // Accumulate patterns for reporting - if (v3Global.opt.stats()) ctx.m_patternStats.accumulate(dfg); -} diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 1ef17ea47..cd0378248 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -34,20 +34,11 @@ namespace V3DfgPasses { // The logic that is represented by the graph is removed from the netlist. // Returns the constructed DfgGraph. std::unique_ptr astToDfg(AstNetlist&, V3DfgContext&) VL_MT_DISABLED; - -// Add DfgVertexAst to the given DfgGraph for all references in the given AstNode. -// The function 'getVarVertex' is used to get the DfgVertexVar for an AstVarScope. -// If it returns nullptr, the reference will be ignored. -void addAstRefs(DfgGraph& dfg, AstNode* nodep, - std::function getVarVertex) VL_MT_DISABLED; - // Remove unobservable variabels and logic that drives only such variables void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; - // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; - // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement @@ -57,27 +48,8 @@ void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // acyclic (flag 'true'), or still cyclic (flag 'false'). std::pair, bool> // breakCycles(const DfgGraph&, V3DfgContext&) VL_MT_DISABLED; - -// Optimize the given DfgGraph -void optimize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; - -// Convert DfgGraph back into Ast, and insert converted graph back into the Ast. -void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; - -//=========================================================================== -// Intermediate/internal operations -//=========================================================================== - // Construct binary to oneHot decoders void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED; -// Populates the given DfgUserMap 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(const DfgGraph&, DfgUserMap&) VL_MT_DISABLED; // Common subexpression elimination void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED; // Inline fully driven variables @@ -88,6 +60,26 @@ void pushDownSels(DfgGraph& dfg, V3DfgPushDownSelsContext& ctx) VL_MT_DISABLED; void peephole(DfgGraph&, V3DfgPeepholeContext&) VL_MT_DISABLED; // Regularize graph. This must be run before converting back to Ast. void regularize(DfgGraph&, V3DfgRegularizeContext&) VL_MT_DISABLED; +// Convert DfgGraph back into Ast, and insert converted graph back into the Ast. +void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; + +//=========================================================================== +// Intermediate/internal operations +//=========================================================================== + +// Add DfgVertexAst to the given DfgGraph for all references in the given AstNode. +// The function 'getVarVertex' is used to get the DfgVertexVar for an AstVarScope. +// If it returns nullptr, the reference will be ignored. +void addAstRefs(DfgGraph& dfg, AstNode* nodep, + std::function getVarVertex) VL_MT_DISABLED; +// Populates the given DfgUserMap 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(const DfgGraph&, DfgUserMap&) VL_MT_DISABLED; // Remove unused nodes void removeUnused(DfgGraph&) VL_MT_DISABLED; // Check all types are consistent. This will not return if there is a type error. diff --git a/src/V3DfgPatternStats.h b/src/V3DfgPatternStats.h index 4e38ec633..8af8a4571 100644 --- a/src/V3DfgPatternStats.h +++ b/src/V3DfgPatternStats.h @@ -18,6 +18,7 @@ #define VERILATOR_V3DFGPATTERNSTATS_H_ #include "V3Dfg.h" +#include "V3File.h" #include #include @@ -149,24 +150,6 @@ class V3DfgPatternStats final { return deep; } -public: - V3DfgPatternStats() = default; - - void accumulate(const DfgGraph& dfg) { - dfg.forEachVertex([&](const DfgVertex& vtx) { - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - std::ostringstream ss; - if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1; - m_internedConsts.clear(); - m_internedVars.clear(); - m_internedSelLsbs.clear(); - m_internedWordWidths.clear(); - m_internedWideWidths.clear(); - m_internedVertices.clear(); - } - }); - } - void dump(std::ostream& os) { using Line = std::pair; for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { @@ -194,6 +177,33 @@ public: os << '\n'; } } + +public: + V3DfgPatternStats() = default; + ~V3DfgPatternStats() { + // File to dump to + const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__stats_dfg_patterns.txt"; + // Open, write, close + const std::unique_ptr ofp{V3File::new_ofstream(filename)}; + if (ofp->fail()) v3fatal("Can't write file: " << filename); + dump(*ofp); + } + + void accumulate(const DfgGraph& dfg) { + dfg.forEachVertex([&](const DfgVertex& vtx) { + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + std::ostringstream ss; + if (render(ss, vtx, i)) m_patterCounts[i][ss.str()] += 1; + m_internedConsts.clear(); + m_internedVars.clear(); + m_internedSelLsbs.clear(); + m_internedWordWidths.clear(); + m_internedWideWidths.clear(); + m_internedVertices.clear(); + } + }); + } }; #endif