Change Dfg pattern dumps to use --dump-dfg-patterns (#7455)

Dumping Dfg patterns can take a non-trivial amount of time, so do it
only with --dump-dfg-patterns, instead of with --stats.
Also further improve dumping format.
This commit is contained in:
Geza Lore
2026-04-21 12:07:19 +01:00
committed by GitHub
parent 2ef979a39c
commit 2b9d006097
11 changed files with 301 additions and 247 deletions
+154
View File
@@ -936,6 +936,160 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) {
delete this;
}
class DfgPatternString final {
std::ostream& m_os;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Multiplicity and depth of vertices
std::map<const DfgVertex*, std::pair<uint32_t, uint32_t>> m_multiplicityAndDepth;
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) {
std::pair<uint32_t, uint32_t>& value = m_multiplicityAndDepth
.emplace(std::piecewise_construct, //
std::forward_as_tuple(&vtx), //
std::forward_as_tuple(0, depth))
.first->second;
value.first += 1;
value.second = std::max(value.second, depth);
if (!depth) return;
vtx.foreachSource([&](const DfgVertex& src) {
recordMultiplicityAndDepth(src, depth - 1);
return false;
});
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) {
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
m_os << "(CONST ZERO)";
} else if (constp->isOnes()) {
m_os << "(CONST ONES)";
} else {
m_os << "(CONST #" << internConst(*constp) << ')';
}
} else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) {
// Base case 2: vertex appearing multiple times
m_os << internVertex(vtx);
} else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) {
// Base case 3: vertex with no inputs (input variable)
m_os << '(' << vtx.typeName() << ')';
} else if (depth == 0) {
// Base case 4: deep vertex (apperaing only once)
m_os << "_";
} else {
// Recursively print an S-expression for the vertex
m_os << '(';
// Name
m_os << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
m_os << '@';
if (selp->lsb() == 0) {
m_os << '0';
} else {
m_os << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
m_os << ' ';
render(src, depth - 1, false);
return false;
});
// S-expression end
m_os << ')';
}
// Annotate type
m_os << ':';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(m_os);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
m_os << '1';
} else if (width <= VL_QUADSIZE) {
m_os << internWordWidth(width);
} else {
m_os << internWideWidth(width);
}
}
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) m_os << '*';
}
public:
DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth)
: m_os{os} {
recordMultiplicityAndDepth(vtx, depth);
render(vtx, depth, false);
using Pair = std::pair<std::string, const DfgVertex*>;
std::vector<Pair> vertices;
for (const auto& pair : m_multiplicityAndDepth) {
if (pair.second.first == 1) continue;
vertices.emplace_back(internVertex(*pair.first), pair.first);
}
std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) { //
return a.first < b.first;
});
for (const Pair& pair : vertices) {
m_os << " | " << pair.first << " is ";
render(*pair.second, m_multiplicityAndDepth.at(pair.second).second);
}
}
};
std::string DfgVertex::patternString(uint32_t depth) const {
std::ostringstream oss;
DfgPatternString{oss, *this, depth};
return oss.str();
}
//------------------------------------------------------------------------------
// DfgVisitor
+3
View File
@@ -352,6 +352,9 @@ public:
// Human-readable name for source operand with given index for debugging
virtual std::string srcName(size_t idx) const = 0;
// S-expression inspired dump of vertex and operands for debugging
std::string patternString(uint32_t depth = 0) const;
};
// DfgVertex visitor
+9 -121
View File
@@ -21,133 +21,27 @@
#include "V3DfgPasses.h"
#include "V3File.h"
#include <algorithm>
#include <map>
#include <unordered_map>
class V3DfgPatternStats final {
static constexpr uint32_t MIN_PATTERN_DEPTH = 1;
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Maps from pattern to the number of times it appears, for each pattern depth
std::vector<std::unordered_map<std::string, size_t>> m_patterCounts{MAX_PATTERN_DEPTH + 1};
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "c");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
bool render(std::ostringstream& ss, const DfgVertex& vtx, uint32_t depth) {
bool deep = depth == 0;
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
ss << "'0";
} else if (constp->isOnes()) {
ss << "'1";
} else {
ss << internConst(*constp);
}
} else if (depth == 0) {
// Base case 2: deep vertex
ss << "_";
} else {
// Recursively print an S-expression for the vertex
// S-expression begin
ss << '(';
// Name
ss << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
ss << '@';
if (selp->lsb() == 0) {
ss << '0';
} else {
ss << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
ss << ' ';
if (render(ss, src, depth - 1)) deep = true;
return false;
});
// S-expression end
ss << ')';
}
// Annotate identity
ss << ":" << internVertex(vtx);
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) ss << '*';
// Annotate type
ss << '/';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(ss);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
ss << '1';
} else if (width <= VL_QUADSIZE) {
ss << internWordWidth(width);
} else {
ss << internWideWidth(width);
}
}
// Done
return deep;
}
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) {
os << "DFG patterns with depth " << i << '\n';
// Pick up pattern accumulators with given depth
const auto& patternCounts = m_patterCounts[i];
auto& patternCounts = m_patterCounts[i];
// Remove patterns also present at shallower depths
for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) {
for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first);
}
// Sort patterns, first by descending frequency, then lexically
std::vector<Line> lines;
@@ -173,8 +67,8 @@ public:
V3DfgPatternStats() = default;
~V3DfgPatternStats() {
// File to dump to
const std::string filename = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix()
+ "__stats_dfg_patterns.txt";
const std::string filename
= v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__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);
@@ -184,13 +78,7 @@ public:
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_internedSelLsbs.clear();
m_internedWordWidths.clear();
m_internedWideWidths.clear();
m_internedVertices.clear();
m_patterCounts[i][vtx.patternString(i)] += 1;
}
});
}
+2 -2
View File
@@ -154,9 +154,9 @@ class DataflowOptimize final {
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
endOfStage("peephole", dfg, acyclicComps);
// Accumulate patterns for reporting
if (v3Global.opt.stats()) {
if (v3Global.opt.dumpDfgPatterns()) {
V3DfgPasses::dumpPatterns(acyclicComps);
endOfStage("patterns");
endOfStage("dumpPatterns");
}
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
endOfStage("pushDownSels", dfg, acyclicComps);
+3
View File
@@ -534,6 +534,9 @@ public:
bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; }
bool dpiHdrOnly() const { return m_dpiHdrOnly; }
bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); }
bool dumpDfgPatterns() const {
return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns");
}
bool dumpTreeDot() const {
return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot");
}