Internals: Add DfgWorklist commonly used in algorithms (#6361)

This commit is contained in:
Geza Lore 2025-09-03 13:55:33 +01:00 committed by GitHub
parent 1e4ede08b6
commit 8bf2240d40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 126 additions and 116 deletions

View File

@ -769,6 +769,77 @@ public:
T_Value& at(const DfgVertex* vtxp) const { return (*this).at(*vtxp); }
};
//------------------------------------------------------------------------------
// Worklist for processing DfgVertices, implemented via DfgUserMap
class DfgWorklist final {
// STATE
// The Graph being processed
DfgGraph& m_dfg;
// Map from vertex to next vertex in the work list
DfgUserMap<DfgVertex*> m_nextp = m_dfg.makeUserMap<DfgVertex*>();
// We want all 'nextp' pointers for vertices that are in the worklist to be
// non-zero (including that of the last element). This allows us to do two
// important things: detect if an element is in the list by checking for a
// non-zero 'nextp'', and easy prefetching without conditionals. The
// address of the worklist itself is a good sentinel as it is a valid
// memory address, and we can easily check for the end of the list.
DfgVertex* const m_sentinelp = reinterpret_cast<DfgVertex*>(this);
// Head of work list
DfgVertex* m_headp = m_sentinelp;
public:
// CONSTRUCTOR
DfgWorklist(DfgGraph& dfg)
: m_dfg{dfg} {}
VL_UNCOPYABLE(DfgWorklist);
VL_UNMOVABLE(DfgWorklist);
~DfgWorklist() = default;
// METHODS
// If 'vtx' is not in the worklist already, add it at the head of the list
// and return ture. If 'vtx' is already in the work list, then do nothing
// and return false.
bool push_front(DfgVertex& vtx) {
// Pick up reference to the next pointer
DfgVertex*& nextpr = m_nextp[vtx];
// If already in work list then nothing to do
if (nextpr) return false;
// Prepend to work list
nextpr = m_headp;
m_headp = &vtx;
return true;
}
// Returns ture iff 'vtx' is in the worklist
bool contains(const DfgVertex& vtx) { return m_nextp[vtx]; }
// Process the worklist by removing the first element, calling on it the
// given callable 'f', and repeat until the worklist is empty. The callable
// 'f' can add furthere vertices to the worklist.
template <typename T_Callable>
void foreach(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<void, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'void(DfgVertex&)'");
// Process the work list
while (m_headp != m_sentinelp) {
// Pick up the head
DfgVertex& vtx = *m_headp;
// Detach the head
m_headp = m_nextp.at(vtx);
// Prefetch next item
VL_PREFETCH_RW(m_headp);
// This item is now off the work list
m_nextp.at(vtx) = nullptr;
// Apply 'f'
f(vtx);
}
}
};
//------------------------------------------------------------------------------
// Inline method definitions

View File

@ -91,23 +91,14 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) {
}
void V3DfgPasses::removeUnused(DfgGraph& dfg) {
// Map from vertex to next vertex in the work list
DfgUserMap<DfgVertex*> nextp = dfg.makeUserMap<DfgVertex*>();
// Head of work list. Note that we want all next pointers in the list to be non-zero (including
// that of the last element). This allows as to do two important things: detect if an element
// is in the list by checking for a non-zero next pointer, and easy prefetching without
// conditionals. The address of the graph is a good sentinel as it is a valid memory address,
// and we can easily check for the end of the list.
DfgVertex* const sentinelp = reinterpret_cast<DfgVertex*>(&dfg);
DfgVertex* workListp = sentinelp;
// Worklist based algoritm
DfgWorklist workList{dfg};
// Add all unused operation vertices to the work list
for (DfgVertex& vtx : dfg.opVertices()) {
if (vtx.hasSinks()) continue;
// This vertex is unused. Add to work list.
nextp[vtx] = workListp;
workListp = &vtx;
workList.push_front(vtx);
}
// Also add all unused temporaries created during synthesis
@ -116,45 +107,33 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
if (vtx.hasSinks()) continue;
if (vtx.hasDfgRefs()) continue;
// This vertex is unused. Add to work list.
nextp[vtx] = workListp;
workListp = &vtx;
workList.push_front(vtx);
}
// Process the work list
while (workListp != sentinelp) {
// Pick up the head
DfgVertex* const vtxp = workListp;
// Detach the head
workListp = nextp.at(vtxp);
// Prefetch next item
VL_PREFETCH_RW(workListp);
// This item is now off the work list
nextp.at(vtxp) = nullptr;
workList.foreach([&](DfgVertex& vtx) {
// DfgLogic should have been synthesized or removed
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "Should not be DfgLogic");
UASSERT_OBJ(!vtx.is<DfgLogic>(), &vtx, "Should not be DfgLogic");
// If used, then nothing to do, so move on
if (vtxp->hasSinks()) continue;
if (vtx.hasSinks()) return;
// If temporary used in another graph, we need to keep it
if (const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>()) {
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
UASSERT_OBJ(varp->tmpForp(), varp, "Non-temporary variable should not be visited");
if (varp->hasDfgRefs()) continue;
if (varp->hasDfgRefs()) return;
}
// Add sources of unused vertex to work list
vtxp->foreachSource([&](DfgVertex& src) {
vtx.foreachSource([&](DfgVertex& src) {
// We only remove actual operation vertices and synthesis temporaries in this loop
if (src.is<DfgConst>()) return false;
const DfgVertexVar* const varp = src.cast<DfgVertexVar>();
if (varp && !varp->tmpForp()) return false;
// If already in work list then nothing to do
if (nextp[src]) return false;
// Actually add to work list.
nextp[src] = workListp;
workListp = &src;
// Add source to workList
workList.push_front(src);
return false;
});
// Remove the unused vertex
vtxp->unlinkDelete(dfg);
}
vtx.unlinkDelete(dfg);
});
// Remove unused and undriven variable vertices
for (DfgVertexVar* const vtxp : dfg.varVertices().unlinkable()) {
@ -409,32 +388,11 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
}
void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// Map from vertex to next vertex in the work list
DfgUserMap<DfgVertex*> nextp = dfg.makeUserMap<DfgVertex*>();
// Worklist based algoritm
DfgWorklist workList{dfg};
// Head of work list. Note that we want all next pointers in the list to be non-zero
// (including that of the last element). This allows us to do two important things: detect
// if an element is in the list by checking for a non-zero next pointer, and easy
// prefetching without conditionals. The address of the graph is a good sentinel as it is a
// valid memory address, and we can easily check for the end of the list.
DfgVertex* const sentinelp = reinterpret_cast<DfgVertex*>(&dfg);
DfgVertex* workListp = sentinelp;
// Add all variables to the initial work list
for (DfgVertexVar& vtx : dfg.varVertices()) {
nextp[vtx] = workListp;
workListp = &vtx;
}
const auto addToWorkList = [&](DfgVertex& vtx) {
// If already in work list then nothing to do
DfgVertex*& nextpr = nextp[vtx];
if (nextpr) return false;
// Actually add to work list.
nextpr = workListp;
workListp = &vtx;
return false;
};
// Add all variables to the work list
for (DfgVertexVar& vtx : dfg.varVertices()) workList.push_front(vtx);
// List of variables (AstVar or AstVarScope) we are replacing
std::vector<AstNode*> replacedVariables;
@ -446,46 +404,40 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
bool doReplace = false;
// Process the work list
while (workListp != sentinelp) {
// Pick up the head of the work list
DfgVertex* const vtxp = workListp;
// Detach the head
workListp = nextp.at(vtxp);
// Reset user pointer so it can be added back to the work list later
nextp.at(vtxp) = nullptr;
// Prefetch next item
VL_PREFETCH_RW(workListp);
workList.foreach([&](DfgVertex& vtx) {
// Remove unused non-variable vertices
if (!vtxp->is<DfgVertexVar>() && !vtxp->hasSinks()) {
if (!vtx.is<DfgVertexVar>() && !vtx.hasSinks()) {
// Add sources of removed vertex to work list
vtxp->foreachSource(addToWorkList);
vtx.foreachSource([&](DfgVertex& src) {
workList.push_front(src);
return false;
});
// Remove the unused vertex
vtxp->unlinkDelete(dfg);
continue;
vtx.unlinkDelete(dfg);
return;
}
// We can only eliminate DfgVarPacked vertices at the moment
DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>();
if (!varp) continue;
DfgVarPacked* const varp = vtx.cast<DfgVarPacked>();
if (!varp) return;
if (!varp->tmpForp()) {
// Can't remove regular variable if it has external drivers
if (!varp->isDrivenFullyByDfg()) continue;
if (!varp->isDrivenFullyByDfg()) return;
} else {
// Can't remove partially driven used temporaries
if (!varp->isDrivenFullyByDfg() && varp->hasSinks()) continue;
if (!varp->isDrivenFullyByDfg() && varp->hasSinks()) return;
}
// Can't remove if referenced external to the module/netlist
if (varp->hasExtRefs()) continue;
if (varp->hasExtRefs()) return;
// Can't remove if written in the module
if (varp->hasModWrRefs()) continue;
if (varp->hasModWrRefs()) return;
// Can't remove if referenced in other DFGs of the same module
if (varp->hasDfgRefs()) continue;
if (varp->hasDfgRefs()) return;
// If it has multiple sinks, it can't be eliminated
if (varp->hasMultipleSinks()) continue;
if (varp->hasMultipleSinks()) return;
if (!varp->hasModRefs()) {
// If it is only referenced in this DFG, it can be removed
@ -504,15 +456,18 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
ctx.m_deleteps.push_back(nodep); // Delete variable at the end
nodep->user2p(driverp->nodep());
} else {
// Otherwise this *is* the canonical var
continue;
// Otherwise this *is* the canonical var, keep it
return;
}
// Add sources of redundant variable to the work list
vtxp->foreachSource(addToWorkList);
vtx.foreachSource([&](DfgVertex& src) {
workList.push_front(src);
return false;
});
// Remove the redundant variable
vtxp->unlinkDelete(dfg);
}
vtx.unlinkDelete(dfg);
});
// Job done if no replacements possible
if (!doReplace) return;

View File

@ -130,16 +130,11 @@ class V3DfgPeephole final : public DfgVisitor {
// STATE
DfgGraph& m_dfg; // The DfgGraph being visited
// Map from vertex to next vertex on work list
DfgUserMap<DfgVertex*> m_nextp = m_dfg.makeUserMap<DfgVertex*>();
V3DfgPeepholeContext& m_ctx; // The config structure
AstNodeDType* const m_bitDType = V3Dfg::dtypePacked(1); // Common, so grab it up front
// Head of work list. Note that we want all next pointers in the list to be non-zero (including
// that of the last element). This allows as to do two important things: detect if an element
// is in the list by checking for a non-zero next pointer, and easy prefetching without
// conditionals. The 'this' pointer is a good sentinel as it is a valid memory address, and we
// can easily check for the end of the list.
DfgVertex* m_workListp = reinterpret_cast<DfgVertex*>(this);
// This is a worklist based algorithm
DfgWorklist m_workList{m_dfg};
// Vertex lookup-table to avoid creating redundant vertices
V3DfgCache m_cache{m_dfg};
@ -157,11 +152,7 @@ class V3DfgPeephole final : public DfgVisitor {
void addToWorkList(DfgVertex* vtxp) {
// We only process actual operation vertices
if (vtxp->is<DfgConst>() || vtxp->is<DfgVertexVar>()) return;
// If already in work list then nothing to do
if (m_nextp[vtxp]) return;
// Actually add to work list.
m_nextp[vtxp] = m_workListp;
m_workListp = vtxp;
m_workList.push_front(*vtxp);
}
void addSourcesToWorkList(DfgVertex* vtxp) {
@ -183,7 +174,7 @@ class V3DfgPeephole final : public DfgVisitor {
addSourcesToWorkList(vtxp);
// If in work list then we can't delete it just yet (as we can't remove from the middle of
// the work list), but it will be deleted when the work list is processed.
if (m_nextp[vtxp]) return;
if (m_workList.contains(*vtxp)) return;
// Otherwise we can delete it now.
// Remove from cache
m_cache.invalidateByValue(vtxp);
@ -1753,27 +1744,20 @@ class V3DfgPeephole final : public DfgVisitor {
// Add all operation vertices to the work list and cache
for (DfgVertex& vtx : m_dfg.opVertices()) {
m_nextp[vtx] = m_workListp;
m_workListp = &vtx;
m_workList.push_front(vtx);
m_cache.cache(&vtx);
}
// Process the work list
while (m_workListp != reinterpret_cast<DfgVertex*>(this)) {
// Pick up the head
DfgVertex* const vtxp = m_workListp;
// Detach the head and prefetch next
m_workListp = m_nextp[vtxp];
VL_PREFETCH_RW(m_workListp);
m_nextp[vtxp] = nullptr;
// Remove unused vertices as we gp
if (!vtxp->hasSinks()) {
deleteVertex(vtxp);
continue;
m_workList.foreach([&](DfgVertex& vtx) {
// Remove unused vertices as we go
if (!vtx.hasSinks()) {
deleteVertex(&vtx);
return;
}
// Transform node (might get deleted in the process)
iterate(vtxp);
}
iterate(&vtx);
});
}
public: