Internals: Add DfgWorklist commonly used in algorithms (#6361)
This commit is contained in:
parent
1e4ede08b6
commit
8bf2240d40
71
src/V3Dfg.h
71
src/V3Dfg.h
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue