Internals: Refactor DFG getCanonicalVariable for reusability (#6094)

This changes hashed names in the generated code, but otherwise no
functional change.
This commit is contained in:
Geza Lore 2025-06-16 12:25:44 +01:00 committed by GitHub
parent 5e5b5ab69d
commit 832629c602
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 101 additions and 73 deletions

View File

@ -55,6 +55,30 @@ void DfgGraph::addGraph(DfgGraph& other) {
m_opVertices.splice(m_opVertices.end(), other.m_opVertices);
}
std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) {
// Construct the tmpNameStub if we have not done so yet
if (m_tmpNameStub.empty()) {
// Use the hash of the graph name (avoid long names and non-identifiers)
const std::string name = V3Hash{m_name}.toString();
// We need to keep every variable globally unique, and graph hashed
// names might not be, so keep a static table to track multiplicity
static std::unordered_map<std::string, uint32_t> s_multiplicity;
m_tmpNameStub += '_' + name + '_' + std::to_string(s_multiplicity[name]++) + '_';
}
// Assemble the globally unique name
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
}
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNodeDType* dtypep) {
// Add AstVar to containing module
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
modulep()->addStmtsp(varp);
// Create and return the corresponding variable vertex
if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, varp};
return new DfgVarPacked{*this, varp};
}
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
// Dump one DfgVertex in Graphviz format
@ -411,6 +435,55 @@ uint32_t DfgVertex::fanout() const {
return result;
}
DfgVarPacked* DfgVertex::getResultVar() {
UASSERT_OBJ(!this->is<DfgVarArray>(), this, "Arrays are not supported by " << __FUNCTION__);
// It's easy if the vertex is already a variable ...
if (DfgVarPacked* const varp = this->cast<DfgVarPacked>()) return varp;
// Inspect existing variables fully written by this vertex, and choose one
DfgVarPacked* resp = nullptr;
this->forEachSink([&resp](DfgVertex& sink) {
DfgVarPacked* const varp = sink.cast<DfgVarPacked>();
if (!varp) return;
if (!varp->isDrivenFullyByDfg()) return;
// Ignore SystemC variables, they cannot participate in expressions or
// be assigned rvalue expressions.
if (varp->varp()->isSc()) return;
// First variable found
if (!resp) {
resp = varp;
return;
}
// Prefer those variables that must be kept anyway
const bool keepOld = resp->keep() || resp->hasDfgRefs();
const bool keepNew = varp->keep() || varp->hasDfgRefs();
if (keepOld != keepNew) {
if (!keepOld) resp = varp;
return;
}
// Prefer those that already have module references
if (resp->hasModRefs() != varp->hasModRefs()) {
if (!resp->hasModRefs()) resp = varp;
return;
}
// Prefer the earlier one in source order
const FileLine& oldFlp = *(resp->fileline());
const FileLine& newFlp = *(varp->fileline());
if (const int cmp = oldFlp.operatorCompare(newFlp)) {
if (cmp > 0) resp = varp;
return;
}
// Prefer the one with the lexically smaller name
if (const int cmp = resp->varp()->name().compare(varp->varp()->name())) {
if (cmp > 0) resp = varp;
return;
}
// 'resp' and 'varp' are all the same, keep using the existing 'resp'
});
return resp;
}
void DfgVertex::unlinkDelete(DfgGraph& dfg) {
// Unlink source edges
forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); });

View File

@ -280,6 +280,10 @@ public:
// Fanout (number of sinks) of this vertex (expensive to compute)
uint32_t fanout() const VL_MT_DISABLED;
// Return a canonical variable vertex that holds the value of this vertex,
// or nullptr if no such variable exists in the graph. This is O(fanout).
DfgVarPacked* getResultVar() VL_MT_DISABLED;
// Unlink from container (graph or builder), then delete this vertex
void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED;
@ -636,7 +640,8 @@ class DfgGraph final {
uint32_t m_userCnt = 0; // Vertex user data generation counter
// Parent of the graph (i.e.: the module containing the logic represented by this graph).
AstModule* const m_modulep;
const string m_name; // Name of graph (for debugging)
const std::string m_name; // Name of graph - need not be unique
std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy
public:
// CONSTRUCTOR
@ -685,6 +690,13 @@ public:
// Add contents of other graph to this graph. Leaves other graph empty.
void addGraph(DfgGraph& other) VL_MT_DISABLED;
// Genarete a unique name. The provided 'prefix' and 'n' values will be part of the name, and
// must be unique (as a pair) in each invocation for this graph.
std::string makeUniqueName(const std::string& prefix, size_t n) VL_MT_DISABLED;
// Create a new variable with the given name and data type
DfgVertexVar* makeNewVar(FileLine*, const std::string& name, AstNodeDType*) VL_MT_DISABLED;
// Split this graph into individual components (unique sub-graphs with no edges between them).
// Also removes any vertices that are not weakly connected to any variable.
// Leaves 'this' graph empty.

View File

@ -43,14 +43,9 @@ public:
class V3DfgRegularizeContext final {
const std::string m_label; // Label to apply to stats
// Used to generate unique names for different DFGs within the same hashed name
std::unordered_map<std::string, uint32_t> m_multiplicity;
public:
VDouble0 m_temporariesIntroduced; // Number of temporaries introduced
std::string tmpNamePrefix(const DfgGraph&); // Return prefix to use for given graph
explicit V3DfgRegularizeContext(const std::string& label)
: m_label{label} {}
~V3DfgRegularizeContext() VL_MT_DISABLED;

View File

@ -26,73 +26,13 @@
VL_DEFINE_DEBUG_FUNCTIONS;
std::string V3DfgRegularizeContext::tmpNamePrefix(const DfgGraph& dfg) {
// cppcheck-suppress unreadVariable // cppcheck bug
V3Hash hash{dfg.modulep()->name()};
hash += m_label;
std::string name = hash.toString();
const uint32_t sequenceNumber = m_multiplicity[name]++;
name += '_' + std::to_string(sequenceNumber);
return name;
}
class DfgRegularize final {
DfgGraph& m_dfg; // The graph being processed
V3DfgRegularizeContext& m_ctx; // The optimization context for stats
// Prefix of temporary variable names
const std::string m_tmpNamePrefix = "__VdfgRegularize_" + m_ctx.tmpNamePrefix(m_dfg) + '_';
size_t m_nTmps = 0; // Number of temporaries added to this graph - for variable names only
// Return canonical variable that can be used to hold the value of this vertex
DfgVarPacked* getCanonicalVariable(DfgVertex& vtx) {
// First gather all existing variables fully written by this vertex. Ignore SystemC
// variables, those cannot act as canonical variables, as they cannot participate in
// expressions or be assigned rvalues.
std::vector<DfgVarPacked*> varVtxps;
vtx.forEachSink([&](DfgVertex& sink) {
if (DfgVarPacked* const varVtxp = sink.cast<DfgVarPacked>()) {
if (varVtxp->isDrivenFullyByDfg() && !varVtxp->varp()->isSc()) {
varVtxps.push_back(varVtxp);
}
}
});
if (!varVtxps.empty()) {
// There is at least one usable, existing variable. Pick the first one in source
// order for deterministic results.
std::stable_sort(varVtxps.begin(), varVtxps.end(),
[](const DfgVarPacked* ap, const DfgVarPacked* bp) {
// Prefer those variables that must be kept anyway
const bool keepA = ap->keep() || ap->hasDfgRefs();
const bool keepB = bp->keep() || bp->hasDfgRefs();
if (keepA != keepB) return keepA;
// Prefer those that already have module references, so we don't
// have to support recursive substitutions.
if (ap->hasModRefs() != bp->hasModRefs()) return ap->hasModRefs();
// Otherwise source order
const FileLine& aFl = *(ap->fileline());
const FileLine& bFl = *(bp->fileline());
if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0;
// Fall back on names if all else fails
return ap->varp()->name() < bp->varp()->name();
});
return varVtxps.front();
}
// We need to introduce a temporary
++m_ctx.m_temporariesIntroduced;
// Add temporary AstVar to containing module
FileLine* const flp = vtx.fileline();
const std::string name = m_tmpNamePrefix + std::to_string(m_nTmps++);
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, vtx.dtypep()};
m_dfg.modulep()->addStmtsp(varp);
// Create and return a variable vertex for the temporary
return new DfgVarPacked{m_dfg, varp};
}
// Insert intermediate variables for vertices with multiple sinks (or use an existing one)
DfgRegularize(DfgGraph& dfg, V3DfgRegularizeContext& ctx)
: m_dfg{dfg}
@ -117,19 +57,27 @@ class DfgRegularize final {
if (!needsIntermediateVariable) continue;
// This is an op which has multiple sinks. Ensure it is assigned to a variable.
DfgVarPacked* const varp = getCanonicalVariable(vtx);
if (varp->arity()) {
// Existing variable
// This is an op that requires a result variable. Ensure it is
// assigned to one, and redirect other sinks read that variable.
if (DfgVarPacked* const varp = vtx.getResultVar()) {
// A variable already exists
UASSERT_OBJ(varp->arity() == 1, varp, "Result variable with multiple drivers");
FileLine* const flp = varp->driverFileLine(0);
varp->sourceEdge(0)->unlinkSource();
varp->resetSources();
vtx.replaceWith(varp);
varp->addDriver(flp, 0, &vtx);
} else {
// Temporary variable
vtx.replaceWith(varp);
varp->addDriver(vtx.fileline(), 0, &vtx);
// Need to create an intermediate variable
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
FileLine* const flp = vtx.fileline();
DfgVarPacked* const newp
= m_dfg.makeNewVar(flp, name, vtx.dtypep())->as<DfgVarPacked>();
++m_nTmps;
++m_ctx.m_temporariesIntroduced;
// Replace vertex with the variable and add back driver
vtx.replaceWith(newp);
newp->addDriver(vtx.fileline(), 0, &vtx);
}
}
}