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.
This commit is contained in:
Geza Lore 2026-04-09 12:32:57 +01:00
parent cf6a2aec19
commit fb66174d80
5 changed files with 104 additions and 111 deletions

View File

@ -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<std::ofstream> ofp{V3File::new_ofstream(filename)};
if (ofp->fail()) v3fatal("Can't write file: " << filename);
m_patternStats.dump(*ofp);
}
}
~V3DfgContext() = default;
};
#endif //VERILATOR_V3DFGCONTEXT_H_

View File

@ -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<std::unique_ptr<DfgGraph>>& componentps) {
// Dump the graphs for debugging
if (VL_UNLIKELY(dumpDfgLevel() >= 5)) {
if (dfg.size() > 0) dfg.dumpDotFilePrefixed(name);
for (const std::unique_ptr<DfgGraph>& 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<DfgGraph>& 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<std::unique_ptr<DfgGraph>> cyclicComps = dfg.extractCyclicComponents("cyclic");
endOfStage("extract-cyclic");
endOfStage("extractCyclic", dfg, cyclicComps);
// Attempt to convert cyclic components into acyclic ones
std::vector<std::unique_ptr<DfgGraph>> 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<std::unique_ptr<DfgGraph>> 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<DfgGraph>& 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<DfgGraph> 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:

View File

@ -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<void()> 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);
}

View File

@ -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<DfgGraph> 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<DfgVertexVar*(AstVarScope*)> 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<std::unique_ptr<DfgGraph>, 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<uint64_t>&) 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<DfgVertexVar*(AstVarScope*)> 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<uint64_t>&) 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.

View File

@ -18,6 +18,7 @@
#define VERILATOR_V3DFGPATTERNSTATS_H_
#include "V3Dfg.h"
#include "V3File.h"
#include <algorithm>
#include <map>
@ -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<std::string, size_t>;
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<std::ofstream> 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