Optimize DfgPeephole until fixed point (#7309)

This commit is contained in:
Geza Lore 2026-03-26 06:56:36 +00:00 committed by GitHub
parent 728ddf3331
commit afa071a822
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 130 additions and 57 deletions

View File

@ -14,10 +14,25 @@
// //
//************************************************************************* //*************************************************************************
// //
// A pattern-matching based optimizer for DfgGraph. This is in some aspects similar to V3Const, but // A pattern-matching based optimizer for DfgGraph. This is in some aspects
// more powerful in that it does not care about ordering combinational statement. This is also less // similar to V3Const, but more powerful in that it does not care about
// broadly applicable than V3Const, as it does not apply to procedural statements with sequential // ordering combinational statement. This is also less broadly applicable
// execution semantics. // than V3Const, as it does not apply to procedural statements with
// sequential execution semantics.
//
// Each pattern can look at a certain number of source vertices to see
// if a simplified form can be introduced. Some patterns also look at the
// immediate sinks of some vertices. Ideally the algorithm should run to
// fixed point (until nothing else changes). To do this efficiently, two
// lists of vertices are maintained:
// - the 'work' list contains vertices to be considered on the current
// iteration
// - the 'iter' list contains vertices whose whole neighborhood could be
// considered on the next iteration
// The 'work' list ensures simple cascading pattern applications are
// handled in a single pass. The 'iter' list ensures the algorithm runs
// to fixed point if no pattern looks deeper across the graph than the
// neighborhood considered on the next iteration.
// //
//************************************************************************* //*************************************************************************
@ -131,16 +146,18 @@ template <> void foldOp<DfgXor> (V3Number& out, const V3Number& lhs, cons
class V3DfgPeephole final : public DfgVisitor { class V3DfgPeephole final : public DfgVisitor {
// TYPES // TYPES
struct VertexInfo final { struct VertexInfo final {
size_t m_workListIndex = 0; // Position of this vertx m_wlist (0 means not in list) size_t m_workListIndex = 0; // Position of this vertx m_workList (0 means not in list)
size_t m_generation = 0; // Generation number of this vertex size_t m_iterListIndex = 0; // Position of this vertx m_iterList (0 means not in list)
size_t m_id = 0; // Unique vertex ID (0 means unassigned) size_t m_generation = 0; // Generation number of this vertex - for uniqueness check
size_t m_id = 0; // Unique vertex ID (0 means unassigned) - for sorting
}; };
// STATE // STATE
DfgGraph& m_dfg; // The DfgGraph being visited DfgGraph& m_dfg; // The DfgGraph being visited
V3DfgPeepholeContext& m_ctx; // The config structure V3DfgPeepholeContext& m_ctx; // The config structure
const DfgDataType& m_bitDType = DfgDataType::packed(1); // Common, so grab it up front const DfgDataType& m_bitDType = DfgDataType::packed(1); // Common, so grab it up front
std::vector<DfgVertex*> m_wlist; // Using a manual work list as need special operations std::vector<DfgVertex*> m_workList; // List of vertices processed in current interation
std::vector<DfgVertex*> m_iterList; // Vertices to start from on next iteration
DfgUserMap<VertexInfo> m_vInfo = m_dfg.makeUserMap<VertexInfo>(); // Map to VertexInfo DfgUserMap<VertexInfo> m_vInfo = m_dfg.makeUserMap<VertexInfo>(); // Map to VertexInfo
V3DfgCache m_cache{m_dfg}; // Vertex cache to avoid creating redundant vertices V3DfgCache m_cache{m_dfg}; // Vertex cache to avoid creating redundant vertices
DfgVertex* m_vtxp = nullptr; // Currently considered vertex DfgVertex* m_vtxp = nullptr; // Currently considered vertex
@ -170,29 +187,31 @@ class V3DfgPeephole final : public DfgVisitor {
// If already on work list, ignore // If already on work list, ignore
if (vInfo.m_workListIndex) return; if (vInfo.m_workListIndex) return;
// Add to work list // Add to work list
vInfo.m_workListIndex = m_wlist.size(); vInfo.m_workListIndex = m_workList.size();
m_wlist.push_back(vtxp); m_workList.push_back(vtxp);
} }
void removeFromWorkList(DfgVertex* vtxp) { void removeFromWorkList(DfgVertex* vtxp) {
VertexInfo& vInfo = m_vInfo[*vtxp]; VertexInfo& vInfo = m_vInfo[*vtxp];
// m_wlist[0] is always nullptr, fine to assign same here // m_workList[0] is always nullptr, fine to assign same here
m_wlist[vInfo.m_workListIndex] = nullptr; m_workList[vInfo.m_workListIndex] = nullptr;
vInfo.m_workListIndex = 0; vInfo.m_workListIndex = 0;
} }
void addSourcesToWorkList(DfgVertex* vtxp) { void addToIterList(DfgVertex* vtxp) {
vtxp->foreachSource([&](DfgVertex& src) { VertexInfo& vInfo = m_vInfo[*vtxp];
addToWorkList(&src); // If already on iter list, ignore
return false; if (vInfo.m_iterListIndex) return;
}); // Add to iter list
vInfo.m_iterListIndex = m_iterList.size();
m_iterList.push_back(vtxp);
} }
void addSinksToWorkList(DfgVertex* vtxp) { void removeFromIterList(DfgVertex* vtxp) {
vtxp->foreachSink([&](DfgVertex& src) { VertexInfo& vInfo = m_vInfo[*vtxp];
addToWorkList(&src); // m_iterList[0] is always nullptr, fine to assign same here
return false; m_iterList[vInfo.m_iterListIndex] = nullptr;
}); vInfo.m_iterListIndex = 0;
} }
void deleteVertex(DfgVertex* vtxp) { void deleteVertex(DfgVertex* vtxp) {
@ -202,6 +221,9 @@ class V3DfgPeephole final : public DfgVisitor {
// Invalidate cache entry // Invalidate cache entry
m_cache.invalidate(vtxp); m_cache.invalidate(vtxp);
// It might be in the iter list, remove it
removeFromIterList(vtxp);
// Gather source vertices - they might be duplicates, make unique using generation number // Gather source vertices - they might be duplicates, make unique using generation number
incrementGeneration(); incrementGeneration();
std::vector<DfgVertex*> srcps; std::vector<DfgVertex*> srcps;
@ -237,8 +259,8 @@ class V3DfgPeephole final : public DfgVisitor {
return srcp->hasSinks(); return srcp->hasSinks();
}); });
// Add used sources to the work list // Add used sources to the iter list - their sinks have changed
for (auto it = srcps.begin(); it != mid; ++it) addToWorkList(*it); for (auto it = srcps.begin(); it != mid; ++it) addToIterList(*it);
// Recursively delete unused sources // Recursively delete unused sources
for (auto it = mid; it != srcps.end(); ++it) { for (auto it = mid; it != srcps.end(); ++it) {
@ -265,7 +287,13 @@ class V3DfgPeephole final : public DfgVisitor {
}; };
if (VL_UNLIKELY(s_debugBisect.stop(debugCallback))) return; if (VL_UNLIKELY(s_debugBisect.stop(debugCallback))) return;
// Remove sinks from cache, their inputs are about to change // Add sources of the original vertex to the iter list - their sinks are changing
m_vtxp->foreachSource([&](DfgVertex& src) {
addToIterList(&src);
return false;
});
// Remove sinks of the original vertex from the cache - their inputs are changing
m_vtxp->foreachSink([&](DfgVertex& dst) { m_vtxp->foreachSink([&](DfgVertex& dst) {
m_cache.invalidate(&dst); m_cache.invalidate(&dst);
return false; return false;
@ -281,12 +309,14 @@ class V3DfgPeephole final : public DfgVisitor {
// Original vertex is now unused, so delete it // Original vertex is now unused, so delete it
deleteVertex(m_vtxp); deleteVertex(m_vtxp);
// Add all sources to the work list // Add new vertex to iter list to consider neighborhood
addSourcesToWorkList(resp); addToIterList(resp);
// Add replacement to the work list // Add new vertex and sinks to work list as likely to match
addToWorkList(resp); addToWorkList(resp);
// Add sinks of replaced vertex to the work list resp->foreachSink([&](DfgVertex& dst) {
addSinksToWorkList(resp); addToWorkList(&dst);
return false;
});
} }
// Create a 32-bit DfgConst vertex // Create a 32-bit DfgConst vertex
@ -1922,48 +1952,91 @@ class V3DfgPeephole final : public DfgVisitor {
// Assign vertex IDs // Assign vertex IDs
m_dfg.forEachVertex([&](DfgVertex& vtx) { m_vInfo[vtx].m_id = ++m_lastId; }); m_dfg.forEachVertex([&](DfgVertex& vtx) { m_vInfo[vtx].m_id = ++m_lastId; });
// Initialize the work list // Initialize the work list and iter list. They can't get bigger than
m_wlist.reserve(m_dfg.size() * 4); // m_dfg.size(), but new vertices are created in the loop, so over alloacte
m_workList.reserve(m_dfg.size() * 2);
m_iterList.reserve(m_dfg.size() * 2);
// Need a nullptr at index 0 so VertexInfo::m_workListIndex can be used to check membership // Need a nullptr at index 0 so VertexInfo::m_*ListIndex == 0 can check membership
m_wlist.push_back(nullptr); m_workList.push_back(nullptr);
m_iterList.push_back(nullptr);
// Add all variable vertices to the work list. Do this first so they are processed last. // Add all variable vertices to the work list. Do this first so they are processed
// This order has a better chance of preserving original variables in case they are needed. // last. This order has a better chance of preserving original variables in case
// they are needed to hold intermediate results.
for (DfgVertexVar& vtx : m_dfg.varVertices()) addToWorkList(&vtx); for (DfgVertexVar& vtx : m_dfg.varVertices()) addToWorkList(&vtx);
// Add all operation vertices to the work list // Add all operation vertices to the work list
for (DfgVertex& vtx : m_dfg.opVertices()) addToWorkList(&vtx); for (DfgVertex& vtx : m_dfg.opVertices()) addToWorkList(&vtx);
// Process the work list // Process iteratively
while (!m_wlist.empty()) { while (true) {
VL_RESTORER(m_vtxp); // Process the work list - keep the placeholder at index 0
while (m_workList.size() > 1) {
VL_RESTORER(m_vtxp);
// Pop up head of work list // Pop up head of work list
m_vtxp = m_wlist.back(); m_vtxp = m_workList.back();
m_wlist.pop_back(); m_workList.pop_back();
if (!m_vtxp) continue; // If removed from worklist, move on if (!m_vtxp) continue; // If removed from worklist (deleted), move on
m_vInfo[m_vtxp].m_workListIndex = 0; // No longer on work list m_vInfo[m_vtxp].m_workListIndex = 0; // No longer on work list
// Variables are special, just visit them, the visit might delete them // Variables are special, just visit them, the visit might delete them
if (DfgVertexVar* const varp = m_vtxp->cast<DfgVertexVar>()) { if (DfgVertexVar* const varp = m_vtxp->cast<DfgVertexVar>()) {
visit(varp); visit(varp);
continue; continue;
}
// Unsued vertices should have been removed immediately
UASSERT_OBJ(m_vtxp->hasSinks(), m_vtxp, "Operation vertex should have sinks");
// Check if an equivalent vertex exists, if so replace this vertex with it
if (DfgVertex* const sampep = m_cache.cache(m_vtxp)) {
APPLYING(REPLACE_WITH_EQUIVALENT) {
replace(sampep);
continue;
}
}
// Visit vertex, might get deleted in the process
iterate(m_vtxp);
} }
// Unsued vertices should have been removed immediately // If nothing was added to the iter list, we can stop
UASSERT_OBJ(m_vtxp->hasSinks(), m_vtxp, "Operation vertex should have sinks"); if (m_iterList.size() == 1) break;
// Check if an equivalent vertex exists, if so replace this vertex with it // Expand the iter list to visit the whole neighborhood of vertices
if (DfgVertex* const sampep = m_cache.cache(m_vtxp)) { // within a fixed number of hops from the enqueued vertices. This
APPLYING(REPLACE_WITH_EQUIVALENT) { // enables patterns that look deeper across the graph to be reconsidered.
replace(sampep); {
continue; size_t begin = 0;
size_t end = m_iterList.size();
for (size_t hops = 1; hops <= 4; ++hops) {
for (size_t i = begin; i < end; ++i) {
DfgVertex* const vtxp = m_iterList[i];
if (!vtxp) continue;
vtxp->foreachSink([&](DfgVertex& dst) {
addToIterList(&dst);
return false;
});
vtxp->foreachSource([&](DfgVertex& src) {
addToIterList(&src);
return false;
});
}
begin = end;
end = m_iterList.size();
} }
} }
// Visit vertex, might get deleted in the process // Move vertices in the iter list to the work list for the next iteration
iterate(m_vtxp); for (DfgVertex* const vtxp : m_iterList) {
if (!vtxp) continue;
removeFromIterList(vtxp);
addToWorkList(vtxp);
}
// Reset the iter list
m_iterList.resize(1);
} }
} }