From 8bf2240d408ffd78715b50ca144917d86fb77c54 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 3 Sep 2025 13:55:33 +0100 Subject: [PATCH] Internals: Add DfgWorklist commonly used in algorithms (#6361) --- src/V3Dfg.h | 71 +++++++++++++++++++++++ src/V3DfgPasses.cpp | 129 ++++++++++++++---------------------------- src/V3DfgPeephole.cpp | 42 +++++--------- 3 files changed, 126 insertions(+), 116 deletions(-) diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 62fc47494..47f3128c3 100644 --- a/src/V3Dfg.h +++ b/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 m_nextp = m_dfg.makeUserMap(); + // 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(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 + void foreach(T_Callable&& f) { + static_assert(vlstd::is_invocable_r::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 diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index f5f64e9ba..76579c131 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -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 nextp = dfg.makeUserMap(); - - // 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(&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(), vtxp, "Should not be DfgLogic"); + UASSERT_OBJ(!vtx.is(), &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()) { + if (const DfgVertexVar* const varp = vtx.cast()) { 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()) return false; const DfgVertexVar* const varp = src.cast(); 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 nextp = dfg.makeUserMap(); + // 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(&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 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() && !vtxp->hasSinks()) { + if (!vtx.is() && !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(); - if (!varp) continue; + DfgVarPacked* const varp = vtx.cast(); + 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; diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 52df0b114..88883e28e 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -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 m_nextp = m_dfg.makeUserMap(); 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(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() || vtxp->is()) 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(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: