Optimize DfgGraph vertex storage

Vertices representing variables (DfgVertexVar) and constants (DfgConst)
are very common (40-50% of all vertices created in some large designs),
and we also need to, or can treat them specially in algorithms. Keep
these as separate lists in DfgGraph for direct access to them. This
improve verilation speed.
This commit is contained in:
Geza Lore 2022-10-07 16:13:01 +01:00
parent 461f3c1004
commit c033a0d7c8
8 changed files with 270 additions and 208 deletions

View File

@ -212,28 +212,35 @@ class ExtractCyclicComponents final {
// Methods for merging
void visitMergeSCCs(const DfgVertex& vtx, size_t targetComponent) {
// We stop at variable boundaries, which is where we will split the graphs
if (vtx.is<DfgVertexVar>()) return;
// Mark visited/move on if already visited
if (!m_merged.insert(&vtx).second) return;
// Assign vertex to the target component
m_state.at(&vtx).component = targetComponent;
// Visit all neighbours
vtx.forEachSource([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); });
vtx.forEachSink([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); });
// Visit all neighbours. We stop at variable boundaries,
// which is where we will split the graphs
vtx.forEachSource([=](const DfgVertex& other) {
if (other.is<DfgVertexVar>()) return;
visitMergeSCCs(other, targetComponent);
});
vtx.forEachSink([=](const DfgVertex& other) {
if (other.is<DfgVertexVar>()) return;
visitMergeSCCs(other, targetComponent);
});
}
void mergeSCCs() {
// Ensure that component boundaries are always at variables, by merging SCCs
m_merged.reserve(m_dfg.size());
m_dfg.forEachVertex([this](DfgVertex& vtx) {
// Start DFS from each vertex that is in a non-trivial SCC, and merge everything that
// is reachable from it into this component.
if (const size_t target = m_state.at(&vtx).component) visitMergeSCCs(vtx, target);
});
// Merging stops at variable boundaries, so we don't need to iterate variables. Constants
// are reachable from their sinks, or ar unused, so we don't need to iterate them either.
for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// Start DFS from each vertex that is in a non-trivial SCC, and merge everything
// that is reachable from it into this component.
if (const size_t target = m_state.at(vtxp).component) visitMergeSCCs(*vtxp, target);
}
}
//==========================================================================
@ -301,7 +308,7 @@ class ExtractCyclicComponents final {
}
// Fix edges that cross components
void fixEdges(DfgVertex& vtx) {
void fixEdges(DfgVertexVar& vtx) {
if (DfgVarPacked* const vvtxp = vtx.cast<DfgVarPacked>()) {
fixSources<DfgVarPacked>(
*vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) {
@ -321,59 +328,55 @@ class ExtractCyclicComponents final {
fixSinks(*vvtxp);
return;
}
if (VL_UNLIKELY(m_doExpensiveChecks)) {
// Non-variable vertex. Just check that edges do not cross components
const size_t component = m_state.at(&vtx).component;
vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) {
DfgVertex& source = *edge.sourcep();
// OK to cross at variables
if (source.is<DfgVertexVar>()) return;
UASSERT_OBJ(component == m_state.at(&source).component, &vtx,
"Component crossing edge without variable involvement");
});
}
}
static void packSources(DfgGraph& dfg) {
// Remove undriven variable sources
dfg.forEachVertex([&](DfgVertex& vtx) {
if (DfgVarPacked* const varp = vtx.cast<DfgVarPacked>()) {
for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>()) {
varp->packSources();
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
return;
}
if (DfgVarArray* const varp = vtx.cast<DfgVarArray>()) {
if (DfgVarArray* const varp = vtxp->cast<DfgVarArray>()) {
varp->packSources();
if (!varp->hasSinks() && varp->arity() == 0) {
VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
}
return;
}
});
}
}
static void checkEdges(DfgGraph& dfg) {
// Check that each edge connects to a vertex that is within the same graph.
// Also check variable vertex sources are all connected.
void checkGraph(DfgGraph& dfg) const {
// Build set of vertices
std::unordered_set<const DfgVertex*> vertices{dfg.size()};
dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); });
// Check that:
// - Edges only cross components at variable boundaries
// - Each edge connects to a vertex that is within the same graph
// - Variable vertex sources are all connected.
dfg.forEachVertex([&](const DfgVertex& vtx) {
const size_t component = m_state.at(&vtx).component;
vtx.forEachSource([&](const DfgVertex& src) {
if (!src.is<DfgVertexVar>()) { // OK to cross at variables
UASSERT_OBJ(component == m_state.at(&src).component, &vtx,
"Edge crossing components without variable involvement");
}
UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph");
});
vtx.forEachSink([&](const DfgVertex& snk) {
if (!snk.is<DfgVertexVar>()) { // OK to cross at variables
UASSERT_OBJ(component == m_state.at(&snk).component, &vtx,
"Edge crossing components without variable involvement");
}
UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph");
});
if (const DfgVarPacked* const vtxp = vtx.cast<DfgVarPacked>()) {
vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) {
UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex");
});
return;
}
if (const DfgVarArray* const vtxp = vtx.cast<DfgVarArray>()) {
if (const DfgVertexVar* const vtxp = vtx.cast<DfgVertexVar>()) {
vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) {
UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex");
});
@ -393,26 +396,45 @@ class ExtractCyclicComponents final {
m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)});
}
// Fix up edges crossing components, and move vertices into their correct component. Note
// that fixing up the edges can create clones. Clones are added to the correct component,
// which also means that they might be added to the original DFG. Clones do not need
// fixing up, but also are not necessarily in the m_state map (in fact they are only there
// in debug mode), so we only iterate up to the original vertices. Because any new vertex
// is added at the end of the vertex list, we can just do this by iterating a fixed number
// of vertices.
size_t vertexCount = m_dfg.size();
m_dfg.forEachVertex([&](DfgVertex& vtx) {
if (!vertexCount) return;
--vertexCount;
// Fix up edges crossing components (we can only do this at variable boundaries, and the
// earlier merging of components ensured crossing in fact only happen at variable
// boundaries). Note that fixing up the edges can create clones of variables. Clones are
// added to the correct component, which also means that they might be added to the
// original DFG. Clones do not need fixing up, but also are not necessarily in the m_state
// map (in fact they are only there in debug mode), so we need to check this.
// Also move vertices into their correct component while we are at it.
for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
// It is possible the last vertex (with a nullptr for 'nextp') gets cloned, and hence
// it's 'nextp' would become none nullptr as the clone is added. However, we don't need
// to iterate clones anyway, so it's ok to get the 'nextp' early in the loop.
nextp = vtxp->verticesNext();
// Clones need not be fixed up
if (!m_state.count(vtxp)) return;
// Fix up the edges crossing components
fixEdges(vtx);
// Move the vertex to the component graph (leave component 0, which is the originally
// acyclic sub-graph, in the original graph)
if (const size_t component = m_state.at(&vtx).component) {
m_dfg.removeVertex(vtx);
m_components[component - 1]->addVertex(vtx);
fixEdges(*vtxp);
// Move the vertex to the component graph (leave component 0, which is the
// originally acyclic sub-graph, in the original graph)
if (const size_t component = m_state.at(vtxp).component) {
m_dfg.removeVertex(*vtxp);
m_components[component - 1]->addVertex(*vtxp);
}
});
}
// Move other vertices to their component graphs
for (DfgConst *vtxp = m_dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (const size_t component = m_state.at(vtxp).component) {
m_dfg.removeVertex(*vtxp);
m_components[component - 1]->addVertex(*vtxp);
}
}
for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (const size_t component = m_state.at(vtxp).component) {
m_dfg.removeVertex(*vtxp);
m_components[component - 1]->addVertex(*vtxp);
}
}
// Pack sources of variables to remove the now undriven inputs
// (cloning might have unlinked some of the inputs),
@ -421,8 +443,8 @@ class ExtractCyclicComponents final {
if (VL_UNLIKELY(m_doExpensiveChecks)) {
// Check results for consistency
checkEdges(m_dfg);
for (const auto& dfgp : m_components) checkEdges(*dfgp);
checkGraph(m_dfg);
for (const auto& dfgp : m_components) checkGraph(*dfgp);
}
}
@ -448,24 +470,6 @@ std::vector<std::unique_ptr<DfgGraph>> DfgGraph::extractCyclicComponents(std::st
return ExtractCyclicComponents::apply(*this, label);
}
void DfgGraph::runToFixedPoint(std::function<bool(DfgVertex&)> f) {
bool changed;
const auto apply = [&](DfgVertex& vtx) -> void {
if (f(vtx)) changed = true;
};
while (true) {
// Do one pass over the graph.
changed = false;
forEachVertex(apply);
if (!changed) break;
// Do another pass in the opposite direction. Alternating directions reduces
// the pathological complexity with left/right leaning trees.
changed = false;
forEachVertexInReverse(apply);
if (!changed) break;
}
}
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
// Dump one DfgVertex in Graphviz format

View File

@ -120,7 +120,15 @@ class DfgGraph final {
};
// MEMBERS
V3List<DfgVertex*> m_vertices; // The vertices in the graph
// Variables and constants make up a significant proportion of vertices (40-50% was observed
// in large designs), and they can often be treated specially in algorithms, which in turn
// enables significant verilation performance gains, so we keep these in separate lists for
// direct access.
V3List<DfgVertex*> m_varVertices; // The variable vertices in the graph
V3List<DfgVertex*> m_constVertices; // The constant vertices in the graph
V3List<DfgVertex*> m_opVertices; // The operation vertices in the graph
size_t m_size = 0; // Number of vertices in the graph
uint32_t m_userCurrent = 0; // Vertex user data generation number currently in use
uint32_t m_userCnt = 0; // Vertex user data generation counter
@ -156,9 +164,13 @@ public:
return UserDataInUse{this};
}
// Access to vertex list for faster iteration in important contexts
DfgVertex* verticesBegin() const { return m_vertices.begin(); }
DfgVertex* verticesRbegin() const { return m_vertices.rbegin(); }
// Access to vertex lists for faster iteration in important contexts
inline DfgVertexVar* varVerticesBeginp() const;
inline DfgVertexVar* varVerticesRbeginp() const;
inline DfgConst* constVerticesBeginp() const;
inline DfgConst* constVerticesRbeginp() const;
inline DfgVertex* opVerticesBeginp() const;
inline DfgVertex* opVerticesRbeginp() const;
// Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices
// in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however
@ -168,14 +180,6 @@ public:
// 'const' variant of 'forEachVertex'. No mutation allowed.
inline void forEachVertex(std::function<void(const DfgVertex&)> f) const;
// Same as 'forEachVertex' but iterates in reverse order.
inline void forEachVertexInReverse(std::function<void(DfgVertex&)> f);
// Returns first vertex of type 'Vertex' that satisfies the given predicate 'p',
// or nullptr if no such vertex exists in the graph.
template <typename Vertex>
inline Vertex* findVertex(std::function<bool(const Vertex&)> p) const;
// Add contents of other graph to this graph. Leaves other graph empty.
void addGraph(DfgGraph& other);
@ -193,10 +197,6 @@ public:
// it was originally connected.
std::vector<std::unique_ptr<DfgGraph>> extractCyclicComponents(std::string label);
// Apply the given function to all vertices in the graph. The function return value
// indicates that a change has been made to the graph. Repeat until no changes reported.
void runToFixedPoint(std::function<bool(DfgVertex&)> f);
// Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of
// the graph which is included in the output.
void dumpDot(std::ostream& os, const string& label = "") const;
@ -537,46 +537,53 @@ public:
void DfgGraph::addVertex(DfgVertex& vtx) {
++m_size;
vtx.m_verticesEnt.pushBack(m_vertices, &vtx);
if (vtx.is<DfgConst>()) {
vtx.m_verticesEnt.pushBack(m_constVertices, &vtx);
} else if (vtx.is<DfgVertexVar>()) {
vtx.m_verticesEnt.pushBack(m_varVertices, &vtx);
} else {
vtx.m_verticesEnt.pushBack(m_opVertices, &vtx);
}
vtx.m_graphp = this;
}
void DfgGraph::removeVertex(DfgVertex& vtx) {
--m_size;
vtx.m_verticesEnt.unlink(m_vertices, &vtx);
if (vtx.is<DfgConst>()) {
vtx.m_verticesEnt.unlink(m_constVertices, &vtx);
} else if (vtx.is<DfgVertexVar>()) {
vtx.m_verticesEnt.unlink(m_varVertices, &vtx);
} else {
vtx.m_verticesEnt.unlink(m_opVertices, &vtx);
}
vtx.m_graphp = nullptr;
}
void DfgGraph::forEachVertex(std::function<void(DfgVertex&)> f) {
for (DfgVertex *vtxp = m_vertices.begin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->m_verticesEnt.nextp();
for (DfgVertex *vtxp = m_varVertices.begin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
f(*vtxp);
}
for (DfgVertex *vtxp = m_constVertices.begin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
f(*vtxp);
}
for (DfgVertex *vtxp = m_opVertices.begin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
f(*vtxp);
}
}
void DfgGraph::forEachVertex(std::function<void(const DfgVertex&)> f) const {
for (const DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) {
for (const DfgVertex* vtxp = m_varVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
f(*vtxp);
}
}
void DfgGraph::forEachVertexInReverse(std::function<void(DfgVertex&)> f) {
for (DfgVertex *vtxp = m_vertices.rbegin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->m_verticesEnt.prevp();
for (const DfgVertex* vtxp = m_constVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
f(*vtxp);
}
}
template <typename Vertex>
Vertex* DfgGraph::findVertex(std::function<bool(const Vertex&)> p) const {
static_assert(std::is_base_of<DfgVertex, Vertex>::value,
"'Vertex' must be subclass of 'DfgVertex'");
for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) {
if (Vertex* const vvtxp = vtxp->cast<Vertex>()) {
if (p(*vvtxp)) return vvtxp;
}
for (const DfgVertex* vtxp = m_opVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) {
f(*vtxp);
}
return nullptr;
}
void DfgVertex::forEachSource(std::function<void(const DfgVertex&)> f) const {
@ -831,6 +838,21 @@ public:
// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes
#include "V3Dfg__gen_auto_classes.h"
DfgVertexVar* DfgGraph::varVerticesBeginp() const {
return static_cast<DfgVertexVar*>(m_varVertices.begin());
}
DfgVertexVar* DfgGraph::varVerticesRbeginp() const {
return static_cast<DfgVertexVar*>(m_varVertices.rbegin());
}
DfgConst* DfgGraph::constVerticesBeginp() const {
return static_cast<DfgConst*>(m_constVertices.begin());
}
DfgConst* DfgGraph::constVerticesRbeginp() const {
return static_cast<DfgConst*>(m_constVertices.rbegin());
}
DfgVertex* DfgGraph::opVerticesBeginp() const { return m_opVertices.begin(); }
DfgVertex* DfgGraph::opVerticesRbeginp() const { return m_opVertices.rbegin(); }
bool DfgVertex::isZero() const {
if (const DfgConst* const constp = cast<DfgConst>()) return constp->isZero();
return false;

View File

@ -355,6 +355,7 @@ class DfgToAstVisitor final : DfgVisitor {
explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx)
: m_modp{dfg.modulep()}
, m_ctx{ctx} {
// Convert the graph back to combinational assignments
// Used by DfgVertex::hash
const auto userDataInUse = dfg.userDataInUse();
@ -362,14 +363,15 @@ class DfgToAstVisitor final : DfgVisitor {
// We can eliminate some variables completely
std::vector<AstVar*> redundantVarps;
// Convert vertices back to assignments
dfg.forEachVertex([&](DfgVertex& vtx) {
// First render variable assignments
for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// If there is no driver (this vertex is an input to the graph), then nothing to do.
if (!vtxp->isDrivenByDfg()) continue;
// Render packed variable assignments
if (const DfgVarPacked* const dfgVarp = vtx.cast<DfgVarPacked>()) {
// DfgVarPacked instances (these might be driving the given AstVar variable)
// If there is no driver (i.e.: this DfgVarPacked is an input to the Dfg), then
// nothing to do
if (!dfgVarp->isDrivenByDfg()) return;
if (const DfgVarPacked* const dfgVarp = vtxp->cast<DfgVarPacked>()) {
// The driver of this DfgVarPacked might drive multiple variables. Only emit one
// assignment from the driver to an arbitrarily chosen canonical variable, and
// assign the other variables from that canonical variable
@ -386,36 +388,40 @@ class DfgToAstVisitor final : DfgVisitor {
redundantVarps.push_back(dfgVarp->varp());
++m_ctx.m_replacedVars;
}
return;
// Done
continue;
}
// Render array variable assignments
if (const DfgVarArray* dfgVarp = vtx.cast<DfgVarArray>()) {
// If there is no driver, then there is nothing to do
if (!dfgVarp->isDrivenByDfg()) return;
if (const DfgVarArray* dfgVarp = vtxp->cast<DfgVarArray>()) {
// We don't canonicalize arrays, so just render the drivers
convertArrayDiver(dfgVarp);
//
return;
// Done
continue;
}
}
// If the vertex is known to be inlined, then nothing else to do
if (inlineVertex(vtx)) return;
// Constants are always inlined, so we only need to iterate proper operations
for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// If the vertex is known to be inlined, then there is nothing to do
if (inlineVertex(*vtxp)) continue;
// Check if this uses a temporary, vs one of the vars rendered above
AstVar* const resultVarp = getResultVar(&vtx);
AstVar* const resultVarp = getResultVar(vtxp);
if (resultVarp->user1()) {
// We introduced a temporary for this DfgVertex
++m_ctx.m_intermediateVars;
FileLine* const flp = vtx.fileline();
FileLine* const flp = vtxp->fileline();
// Just render the logic
AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(&vtx);
// The lhs is a temporary
AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(vtxp);
// The lhs is the temporary
AstNodeMath* const lhsp = new AstVarRef{flp, resultVarp, VAccess::WRITE};
// Add assignment of the value to the variable
addResultEquation(flp, lhsp, rhsp);
}
});
}
// Remap all references to point to the canonical variables, if one exists
VNDeleter deleter;

View File

@ -84,16 +84,37 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
// Used by DfgVertex::hash
const auto userDataInUse = dfg.userDataInUse();
// In reverse, as the graph is sometimes in reverse topological order already
for (DfgVertex *vtxp = dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesPrev();
// Pre-hash variables for speed, these are all unique, so just set their hash to a unique value
uint32_t varHash = 0;
for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
vtxp->user<V3Hash>() = V3Hash{++varHash};
}
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
// expressions using the same constants to be combined, so we do need to hash equal constants
// to equal values.
for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// Get rid of unused constants while we are at it
if (!vtxp->hasSinks()) {
vtxp->unlinkDelete(dfg);
continue;
}
vtxp->user<V3Hash>() = vtxp->num().toHash();
}
// Combine operation vertices
for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// Get rid of unused operations while we are at it
if (!vtxp->hasSinks()) {
vtxp->unlinkDelete(dfg);
continue;
}
const V3Hash hash = vtxp->hash();
if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
// Don't merge constants
if (vtxp->is<DfgConst>()) continue;
// For everything else...
std::vector<DfgVertex*>& vec = verticesWithEqualHashes[vtxp->hash()];
std::vector<DfgVertex*>& vec = verticesWithEqualHashes[hash];
bool replaced = false;
for (DfgVertex* const candidatep : vec) {
if (candidatep->equals(*vtxp, equalsCache)) {
@ -109,23 +130,37 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
}
}
void V3DfgPasses::inlineVars(DfgGraph& dfg) {
for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>()) {
if (varp->hasSinks() && varp->isDrivenFullyByDfg()) {
DfgVertex* const driverp = varp->source(0);
varp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); });
}
}
}
}
void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) {
dfg.forEachVertex([&](DfgVertex& vtx) {
// We can eliminate certain redundant DfgVarPacked vertices
DfgVarPacked* const varp = vtx.cast<DfgVarPacked>();
if (!varp) return;
for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
// We can only eliminate DfgVarPacked vertices at the moment
DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>();
if (!varp) continue;
// Can't remove if it has consumers
if (varp->hasSinks()) return;
if (varp->hasSinks()) continue;
// Can't remove if read in the module and driven here (i.e.: it's an output of the DFG)
if (varp->hasModRefs() && varp->isDrivenByDfg()) return;
if (varp->hasModRefs() && varp->isDrivenByDfg()) continue;
// Can't remove if only partially driven by the DFG
if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) return;
if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) continue;
// Can't remove if referenced externally, or other special reasons
if (varp->keep()) return;
if (varp->keep()) continue;
// If the driver of this variable has multiple non-variable sinks, then we would need
// a temporary when rendering the graph. Instead of introducing a temporary, keep the
@ -144,7 +179,7 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) {
return firstSinkVarp && nonVarSinks >= 2;
});
// Keep this DfgVarPacked if needed
if (keepFirst && firstSinkVarp == varp) return;
if (keepFirst && firstSinkVarp == varp) continue;
}
// OK, we can delete this DfgVarPacked
@ -155,22 +190,41 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) {
if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree();
// Unlink and delete vertex
vtx.unlinkDelete(dfg);
});
varp->unlinkDelete(dfg);
}
}
void V3DfgPasses::removeUnused(DfgGraph& dfg) {
const auto processVertex = [&](DfgVertex& vtx) {
// Keep variables
if (vtx.is<DfgVertexVar>()) return false;
// Keep if it has sinks
if (vtx.hasSinks()) return false;
// Unlink and delete vertex
vtx.unlinkDelete(dfg);
return true;
};
// Iteratively remove operation vertices
while (true) {
// Do one pass over the graph.
bool changed = false;
for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (!vtxp->hasSinks()) {
changed = true;
vtxp->unlinkDelete(dfg);
}
}
if (!changed) break;
// Do another pass in the opposite direction. Alternating directions reduces
// the pathological complexity with left/right leaning trees.
changed = false;
for (DfgVertex *vtxp = dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesPrev();
if (!vtxp->hasSinks()) {
changed = true;
vtxp->unlinkDelete(dfg);
}
}
if (!changed) break;
}
dfg.runToFixedPoint(processVertex);
// Finally remove unused constants
for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (!vtxp->hasSinks()) vtxp->unlinkDelete(dfg);
}
}
void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
@ -193,12 +247,12 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input");
apply(3, "input ", [&]() {});
apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); });
apply(4, "inlineVars ", [&]() { inlineVars(dfg); });
if (v3Global.opt.fDfgPeephole()) {
apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); });
// Without peephole no variables will be redundant, and we just did CSE, so skip these
apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); });
apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); });
}
apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); });
apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); });
apply(3, "optimized ", [&]() { removeUnused(dfg); });
if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized");
}

View File

@ -102,6 +102,8 @@ AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&);
// Common subexpression elimination
void cse(DfgGraph&, V3DfgCseContext&);
// Inline fully driven variables
void inlineVars(DfgGraph&);
// Peephole optimizations
void peephole(DfgGraph&, V3DfgPeepholeContext&);
// Remove redundant variables

View File

@ -1544,34 +1544,12 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
//=========================================================================
// DfgVertexVar
//=========================================================================
void visit(DfgVarPacked* vtxp) override {
// Inline variables fully driven by the logic represented by the DFG
if (vtxp->hasSinks() && vtxp->isDrivenFullyByDfg()) {
APPLYING(INLINE_VAR) {
// Make consumers of the DfgVar consume the driver directly
DfgVertex* const driverp = vtxp->source(0);
vtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); });
}
}
}
#undef APPLYING
// Process one vertex. Return true if graph changed
void processVertex(DfgVertex* vtxp) {
// Keep DfgVertexVar vertices in this pass. We will remove them later if they become
// redundant. We want to keep the original variables for non-var vertices that drive
// multiple sinks (otherwise we would need to introduce a temporary, but it is better for
// debugging to keep the original variable name, if one is available), so we can't remove
// redundant variables here.
const bool keep = vtxp->is<DfgVertexVar>();
// If it has no sinks (unused), we can remove it
if (!keep && !vtxp->hasSinks()) {
if (!vtxp->hasSinks()) {
vtxp->unlinkDelete(m_dfg);
m_changed = true;
return;
@ -1581,7 +1559,7 @@ class V3DfgPeephole final : public DfgVisitor {
iterate(vtxp);
// If it became unused, we can remove it
if (!keep && !vtxp->hasSinks()) {
if (!vtxp->hasSinks()) {
UASSERT_OBJ(m_changed, vtxp, "'m_changed' must be set if node became unused");
vtxp->unlinkDelete(m_dfg);
}
@ -1594,15 +1572,9 @@ class V3DfgPeephole final : public DfgVisitor {
while (true) {
// Do one pass over the graph in the forward direction.
m_changed = false;
for (DfgVertex *vtxp = m_dfg.verticesBegin(), *nextp; vtxp; vtxp = nextp) {
for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNext();
if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
// Special case DfgConst as it's common and the is nothing we can do about them.
// No need to set 'm_changed' when deleting it as it influences nothing else.
if (vtxp->is<DfgConst>()) {
if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg);
continue;
}
processVertex(vtxp);
}
if (!m_changed) break;
@ -1610,15 +1582,9 @@ class V3DfgPeephole final : public DfgVisitor {
// Do another pass in the opposite direction. Alternating directions reduces
// the pathological complexity with left/right leaning trees.
m_changed = false;
for (DfgVertex *vtxp = m_dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) {
for (DfgVertex *vtxp = m_dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesPrev();
if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp);
// Special case DfgConst as it's common and the is nothing we can do about them.
// No need to set 'm_changed' when deleting it as it influences nothing else.
if (vtxp->is<DfgConst>()) {
if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg);
continue;
}
processVertex(vtxp);
}
if (!m_changed) break;

View File

@ -33,7 +33,6 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \

View File

@ -52,6 +52,15 @@ public:
, m_varp{varp} {}
ASTGEN_MEMBERS_DfgVertexVar;
DfgVertexVar* verticesNext() const {
return static_cast<DfgVertexVar*>(DfgVertex::verticesNext());
}
DfgVertexVar* verticesPrev() const {
return static_cast<DfgVertexVar*>(DfgVertex::verticesPrev());
}
bool isDrivenByDfg() const { return arity() > 0; }
AstVar* varp() const { return m_varp; }
bool hasModRefs() const { return m_hasModRefs; }
void setHasModRefs() { m_hasModRefs = true; }
@ -93,6 +102,9 @@ public:
, m_num{flp, static_cast<int>(width), value} {}
ASTGEN_MEMBERS_DfgConst;
DfgConst* verticesNext() const { return static_cast<DfgConst*>(DfgVertex::verticesNext()); }
DfgConst* verticesPrev() const { return static_cast<DfgConst*>(DfgVertex::verticesPrev()); }
V3Number& num() { return m_num; }
const V3Number& num() const { return m_num; }
@ -167,8 +179,6 @@ public:
}
ASTGEN_MEMBERS_DfgVarArray;
bool isDrivenByDfg() const { return arity() > 0; }
void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) {
m_driverData.emplace_back(flp, index);
DfgVertexVariadic::addSource()->relinkSource(vtxp);
@ -224,7 +234,6 @@ public:
: DfgVertexVar{dfg, dfgType(), varp, 1u} {}
ASTGEN_MEMBERS_DfgVarPacked;
bool isDrivenByDfg() const { return arity() > 0; }
bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); }
void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) {