verilator/src/V3GraphAcyc.cpp

577 lines
24 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Graph acyclic algorithm
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2020 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Global.h"
#include "V3Graph.h"
#include <algorithm>
#include <list>
#include <vector>
//######################################################################
//######################################################################
// Algorithms - acyclic
// Break the minimal number of backward edges to make the graph acyclic
class GraphAcycVertex : public V3GraphVertex {
// user() is used for various sub-algorithm pieces
V3GraphVertex* m_origVertexp; // Pointer to first vertex this represents
protected:
friend class GraphAcyc;
V3ListEnt<GraphAcycVertex*> m_work; // List of vertices with optimization work left
uint32_t m_storedRank = 0; // Rank held until commit to edge placement
bool m_onWorkList = false; // True if already on list of work to do
bool m_deleted = false; // True if deleted
public:
GraphAcycVertex(V3Graph* graphp, V3GraphVertex* origVertexp)
: V3GraphVertex{graphp}
, m_origVertexp{origVertexp} {}
virtual ~GraphAcycVertex() override = default;
V3GraphVertex* origVertexp() const { return m_origVertexp; }
void setDelete() { m_deleted = true; }
bool isDelete() const { return m_deleted; }
virtual string name() const override { return m_origVertexp->name(); }
virtual string dotColor() const override { return m_origVertexp->dotColor(); }
virtual FileLine* fileline() const override { return m_origVertexp->fileline(); }
};
//--------------------------------------------------------------------
class GraphAcycEdge : public V3GraphEdge {
// userp() is always used to point to the head original graph edge
private:
typedef std::list<V3GraphEdge*> OrigEdgeList; // List of orig edges, see also GraphAcyc's decl
V3GraphEdge* origEdgep() const {
OrigEdgeList* oEListp = static_cast<OrigEdgeList*>(userp());
if (!oEListp) v3fatalSrc("No original edge associated with acyc edge " << this << endl);
return (oEListp->front());
}
public:
GraphAcycEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight,
bool cutable = false)
: V3GraphEdge{graphp, fromp, top, weight, cutable} {}
virtual ~GraphAcycEdge() override = default;
// yellow=we might still cut it, else oldEdge: yellowGreen=made uncutable, red=uncutable
virtual string dotColor() const override {
return (cutable() ? "yellow" : origEdgep()->dotColor());
}
};
//--------------------------------------------------------------------
struct GraphAcycEdgeCmp {
inline bool operator()(const V3GraphEdge* lhsp, const V3GraphEdge* rhsp) const {
if (lhsp->weight() > rhsp->weight()) return true; // LHS goes first
if (lhsp->weight() < rhsp->weight()) return false; // RHS goes first
return false;
}
};
//--------------------------------------------------------------------
// CLASSES
class GraphAcyc {
private:
typedef std::list<V3GraphEdge*>
OrigEdgeList; // List of orig edges, see also GraphAcycEdge's decl
// GRAPH USERS
// origGraph
// GraphVertex::user() GraphAycVerted* New graph node
// m_breakGraph
// GraphEdge::user() OrigEdgeList* Old graph edges
// GraphVertex::user bool Detection of loops in simplifyDupIterate
// MEMBERS
V3Graph* m_origGraphp; // Original graph
V3Graph m_breakGraph; // Graph with only breakable edges represented
V3List<GraphAcycVertex*> m_work; // List of vertices with optimization work left
std::vector<OrigEdgeList*> m_origEdgeDelp; // List of deletions to do when done
V3EdgeFuncP m_origEdgeFuncp; // Function that says we follow this edge (in original graph)
uint32_t m_placeStep = 0; // Number that user() must be equal to to indicate processing
static int debug() { return V3Graph::debug(); }
// METHODS
void buildGraph(V3Graph* origGraphp);
void buildGraphIterate(V3GraphVertex* overtexp, GraphAcycVertex* avertexp);
void simplify(bool allowCut);
void simplifyNone(GraphAcycVertex* avertexp);
void simplifyOne(GraphAcycVertex* avertexp);
void simplifyOut(GraphAcycVertex* avertexp);
void simplifyDup(GraphAcycVertex* avertexp);
void cutBasic(GraphAcycVertex* avertexp);
void cutBackward(GraphAcycVertex* avertexp);
void deleteMarked();
void place();
void placeTryEdge(V3GraphEdge* edgep);
bool placeIterate(GraphAcycVertex* vertexp, uint32_t currentRank);
inline bool origFollowEdge(V3GraphEdge* edgep) {
return (edgep->weight() && (m_origEdgeFuncp)(edgep));
}
V3GraphEdge* edgeFromEdge(V3GraphEdge* oldedgep, V3GraphVertex* fromp, V3GraphVertex* top) {
// Make new breakGraph edge, with old edge as a template
GraphAcycEdge* newEdgep = new GraphAcycEdge(&m_breakGraph, fromp, top, oldedgep->weight(),
oldedgep->cutable());
newEdgep->userp(oldedgep->userp()); // Keep pointer to OrigEdgeList
return newEdgep;
}
void addOrigEdgep(V3GraphEdge* toEdgep, V3GraphEdge* addEdgep) {
// Add addEdge (or it's list) to list of edges that break edge represents
// Note addEdge may already have a bunch of similar linked edge representations. Yuk.
UASSERT(addEdgep, "Adding nullptr");
if (!toEdgep->userp()) {
OrigEdgeList* oep = new OrigEdgeList;
m_origEdgeDelp.push_back(oep);
toEdgep->userp(oep);
}
OrigEdgeList* oEListp = static_cast<OrigEdgeList*>(toEdgep->userp());
if (OrigEdgeList* addListp = static_cast<OrigEdgeList*>(addEdgep->userp())) {
for (const auto& itr : *addListp) oEListp->push_back(itr);
addListp->clear(); // Done with it
} else {
oEListp->push_back(addEdgep);
}
}
void cutOrigEdge(V3GraphEdge* breakEdgep, const char* why) {
// From the break edge, cut edges in original graph it represents
UINFO(8, why << " CUT " << breakEdgep->fromp() << endl);
breakEdgep->cut();
OrigEdgeList* oEListp = static_cast<OrigEdgeList*>(breakEdgep->userp());
if (!oEListp) {
v3fatalSrc("No original edge associated with cutting edge " << breakEdgep << endl);
}
// The breakGraph edge may represent multiple real edges; cut them all
for (const auto& origEdgep : *oEListp) {
origEdgep->cut();
UINFO(8,
" " << why << " " << origEdgep->fromp() << " ->" << origEdgep->top() << endl);
}
}
// Work Queue
void workPush(V3GraphVertex* vertexp) {
GraphAcycVertex* avertexp = static_cast<GraphAcycVertex*>(vertexp);
// Add vertex to list of nodes needing further optimization trials
if (!avertexp->m_onWorkList) {
avertexp->m_onWorkList = true;
avertexp->m_work.pushBack(m_work, avertexp);
}
}
GraphAcycVertex* workBeginp() { return m_work.begin(); }
void workPop() {
GraphAcycVertex* avertexp = workBeginp();
avertexp->m_onWorkList = false;
avertexp->m_work.unlink(m_work, avertexp);
}
public:
// CONSTRUCTORS
GraphAcyc(V3Graph* origGraphp, V3EdgeFuncP edgeFuncp)
: m_origGraphp{origGraphp}
, m_origEdgeFuncp{edgeFuncp} {}
~GraphAcyc() {
for (OrigEdgeList* ip : m_origEdgeDelp) delete ip;
m_origEdgeDelp.clear();
}
void main();
};
//--------------------------------------------------------------------
void GraphAcyc::buildGraph(V3Graph* origGraphp) {
// Presumes the graph has been strongly ordered,
// and thus there's a unique color if there are loops in this subgraph.
// For each old node, make a new graph node for optimization
origGraphp->userClearVertices();
origGraphp->userClearEdges();
for (V3GraphVertex* overtexp = origGraphp->verticesBeginp(); overtexp;
overtexp = overtexp->verticesNextp()) {
if (overtexp->color()) {
GraphAcycVertex* avertexp = new GraphAcycVertex(&m_breakGraph, overtexp);
overtexp->userp(avertexp); // Stash so can look up later
}
}
// Build edges between logic vertices
for (V3GraphVertex* overtexp = origGraphp->verticesBeginp(); overtexp;
overtexp = overtexp->verticesNextp()) {
if (overtexp->color()) {
GraphAcycVertex* avertexp = static_cast<GraphAcycVertex*>(overtexp->userp());
buildGraphIterate(overtexp, avertexp);
}
}
}
void GraphAcyc::buildGraphIterate(V3GraphVertex* overtexp, GraphAcycVertex* avertexp) {
// Make new edges
for (V3GraphEdge* edgep = overtexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
if (origFollowEdge(edgep)) { // not cut
V3GraphVertex* toVertexp = edgep->top();
if (toVertexp->color()) {
GraphAcycVertex* toAVertexp = static_cast<GraphAcycVertex*>(toVertexp->userp());
// Replicate the old edge into the new graph
// There may be multiple edges between same pairs of vertices
V3GraphEdge* breakEdgep = new GraphAcycEdge(&m_breakGraph, avertexp, toAVertexp,
edgep->weight(), edgep->cutable());
addOrigEdgep(breakEdgep, edgep); // So can find original edge
}
}
}
}
void GraphAcyc::simplify(bool allowCut) {
// Add all nodes to list of work to do
for (V3GraphVertex* vertexp = m_breakGraph.verticesBeginp(); vertexp;
vertexp = vertexp->verticesNextp()) {
workPush(vertexp);
}
// Optimize till everything finished
while (GraphAcycVertex* vertexp = workBeginp()) {
workPop();
simplifyNone(vertexp);
simplifyOne(vertexp);
simplifyOut(vertexp);
simplifyDup(vertexp);
if (allowCut) {
// The main algorithm works without these, though slower
// So if changing the main algorithm, comment these out for a test run
if (v3Global.opt.oAcycSimp()) {
cutBasic(vertexp);
cutBackward(vertexp);
}
}
}
deleteMarked();
}
void GraphAcyc::deleteMarked() {
// Delete nodes marked for removal
for (V3GraphVertex *nextp, *vertexp = m_breakGraph.verticesBeginp(); vertexp;
vertexp = nextp) {
nextp = vertexp->verticesNextp();
GraphAcycVertex* avertexp = static_cast<GraphAcycVertex*>(vertexp);
if (avertexp->isDelete()) {
VL_DO_DANGLING(avertexp->unlinkDelete(&m_breakGraph), avertexp);
}
}
}
void GraphAcyc::simplifyNone(GraphAcycVertex* avertexp) {
// Don't need any vertices with no inputs, There's no way they can have a loop.
// Likewise, vertices with no outputs
if (avertexp->isDelete()) return;
if (avertexp->inEmpty() || avertexp->outEmpty()) {
UINFO(9, " SimplifyNoneRemove " << avertexp << endl);
avertexp->setDelete(); // Mark so we won't delete it twice
// Remove edges
while (V3GraphEdge* edgep = avertexp->outBeginp()) {
V3GraphVertex* otherVertexp = edgep->top();
// UINFO(9, " out " << otherVertexp << endl);
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
workPush(otherVertexp);
}
while (V3GraphEdge* edgep = avertexp->inBeginp()) {
V3GraphVertex* otherVertexp = edgep->fromp();
// UINFO(9, " in " << otherVertexp << endl);
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
workPush(otherVertexp);
}
}
}
void GraphAcyc::simplifyOne(GraphAcycVertex* avertexp) {
// If a node has one input and one output, we can remove it and change the edges
if (avertexp->isDelete()) return;
if (avertexp->inSize1() && avertexp->outSize1()) {
V3GraphEdge* inEdgep = avertexp->inBeginp();
V3GraphEdge* outEdgep = avertexp->outBeginp();
V3GraphVertex* inVertexp = inEdgep->fromp();
V3GraphVertex* outVertexp = outEdgep->top();
// The in and out may be the same node; we'll make a loop
// The in OR out may be THIS node; we can't delete it then.
if (inVertexp != avertexp && outVertexp != avertexp) {
UINFO(9, " SimplifyOneRemove " << avertexp << endl);
avertexp->setDelete(); // Mark so we won't delete it twice
// Make a new edge connecting the two vertices directly
// If both are breakable, we pick the one with less weight, else it's arbitrary
// We can forget about the origEdge list for the "non-selected" set of edges,
// as we need to break only one set or the other set of edges, not both.
// (This is why we must give preference to the cutable set.)
V3GraphEdge* templateEdgep
= ((inEdgep->cutable()
&& (!outEdgep->cutable() || inEdgep->weight() < outEdgep->weight()))
? inEdgep
: outEdgep);
// cppcheck-suppress leakReturnValNotUsed
edgeFromEdge(templateEdgep, inVertexp, outVertexp);
// Remove old edge
VL_DO_DANGLING(inEdgep->unlinkDelete(), inEdgep);
VL_DO_DANGLING(outEdgep->unlinkDelete(), outEdgep);
VL_DANGLING(templateEdgep);
workPush(inVertexp);
workPush(outVertexp);
}
}
}
void GraphAcyc::simplifyOut(GraphAcycVertex* avertexp) {
// If a node has one output that's not cutable, all its inputs can be reassigned
// to the next node in the list
if (avertexp->isDelete()) return;
if (avertexp->outSize1()) {
V3GraphEdge* outEdgep = avertexp->outBeginp();
if (!outEdgep->cutable()) {
V3GraphVertex* outVertexp = outEdgep->top();
UINFO(9, " SimplifyOutRemove " << avertexp << endl);
avertexp->setDelete(); // Mark so we won't delete it twice
for (V3GraphEdge *nextp, *inEdgep = avertexp->inBeginp(); inEdgep; inEdgep = nextp) {
nextp = inEdgep->inNextp();
V3GraphVertex* inVertexp = inEdgep->fromp();
if (inVertexp == avertexp) {
if (debug()) v3error("Non-cutable edge forms a loop, vertex=" << avertexp);
v3error("Circular logic when ordering code (non-cutable edge loop)");
m_origGraphp->reportLoops(
&V3GraphEdge::followNotCutable,
avertexp->origVertexp()); // calls OrderGraph::loopsVertexCb
// Things are unlikely to end well at this point,
// but we'll try something to get to further errors...
inEdgep->cutable(true);
return;
}
// Make a new edge connecting the two vertices directly
// cppcheck-suppress leakReturnValNotUsed
edgeFromEdge(inEdgep, inVertexp, outVertexp);
// Remove old edge
VL_DO_DANGLING(inEdgep->unlinkDelete(), inEdgep);
workPush(inVertexp);
}
VL_DO_DANGLING(outEdgep->unlinkDelete(), outEdgep);
workPush(outVertexp);
}
}
}
void GraphAcyc::simplifyDup(GraphAcycVertex* avertexp) {
// Remove redundant edges
if (avertexp->isDelete()) return;
// Clear marks
for (V3GraphEdge* edgep = avertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
edgep->top()->userp(nullptr);
}
// Mark edges and detect duplications
for (V3GraphEdge *nextp, *edgep = avertexp->outBeginp(); edgep; edgep = nextp) {
nextp = edgep->outNextp();
V3GraphVertex* outVertexp = edgep->top();
V3GraphEdge* prevEdgep = static_cast<V3GraphEdge*>(outVertexp->userp());
if (prevEdgep) {
if (!prevEdgep->cutable()) {
// !cutable duplicates prev !cutable: we can ignore it, redundant
// cutable duplicates prev !cutable: know it's not a relevant loop, ignore it
UINFO(8, " DelDupEdge " << avertexp << " -> " << edgep->top() << endl);
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
} else if (!edgep->cutable()) {
// !cutable duplicates prev cutable: delete the earlier cutable
UINFO(8, " DelDupPrev " << avertexp << " -> " << prevEdgep->top() << endl);
VL_DO_DANGLING(prevEdgep->unlinkDelete(), prevEdgep);
outVertexp->userp(edgep);
} else {
// cutable duplicates prev cutable: combine weights
UINFO(8, " DelDupComb " << avertexp << " -> " << edgep->top() << endl);
prevEdgep->weight(prevEdgep->weight() + edgep->weight());
addOrigEdgep(prevEdgep, edgep);
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
workPush(outVertexp);
workPush(avertexp);
} else {
// No previous assignment
outVertexp->userp(edgep);
}
}
}
void GraphAcyc::cutBasic(GraphAcycVertex* avertexp) {
// Detect and cleanup any loops from node to itself
if (avertexp->isDelete()) return;
for (V3GraphEdge *nextp, *edgep = avertexp->outBeginp(); edgep; edgep = nextp) {
nextp = edgep->outNextp();
if (edgep->cutable() && edgep->top() == avertexp) {
cutOrigEdge(edgep, " Cut Basic");
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
workPush(avertexp);
}
}
}
void GraphAcyc::cutBackward(GraphAcycVertex* avertexp) {
// If a cutable edge is from A->B, and there's a non-cutable edge B->A, then must cut!
if (avertexp->isDelete()) return;
// Clear marks
for (V3GraphEdge* edgep = avertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
edgep->top()->user(false);
}
for (V3GraphEdge* edgep = avertexp->inBeginp(); edgep; edgep = edgep->inNextp()) {
if (!edgep->cutable()) edgep->fromp()->user(true);
}
// Detect duplications
for (V3GraphEdge *nextp, *edgep = avertexp->outBeginp(); edgep; edgep = nextp) {
nextp = edgep->outNextp();
if (edgep->cutable() && edgep->top()->user()) {
cutOrigEdge(edgep, " Cut A->B->A");
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
workPush(avertexp);
}
}
}
void GraphAcyc::place() {
// Input is m_breakGraph with ranks already assigned on non-breakable edges
// Make a list of all cutable edges in the graph
int numEdges = 0;
for (V3GraphVertex* vertexp = m_breakGraph.verticesBeginp(); vertexp;
vertexp = vertexp->verticesNextp()) {
for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
if (edgep->weight() && edgep->cutable()) ++numEdges;
}
}
UINFO(4, " Cutable edges = " << numEdges << endl);
std::vector<V3GraphEdge*> edges; // List of all edges to be processed
// Make the vector properly sized right off the bat -- faster than reallocating
edges.reserve(numEdges + 1);
for (V3GraphVertex* vertexp = m_breakGraph.verticesBeginp(); vertexp;
vertexp = vertexp->verticesNextp()) {
vertexp->user(0); // Clear in prep of next step
for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
if (edgep->weight() && edgep->cutable()) edges.push_back(edgep);
}
}
// Sort by weight, then by vertex (so that we completely process one vertex, when possible)
stable_sort(edges.begin(), edges.end(), GraphAcycEdgeCmp());
// Process each edge in weighted order
m_placeStep = 10;
for (V3GraphEdge* edgep : edges) placeTryEdge(edgep);
}
void GraphAcyc::placeTryEdge(V3GraphEdge* edgep) {
// Try to make this edge uncutable
m_placeStep++;
UINFO(8, " PlaceEdge s" << m_placeStep << " w" << edgep->weight() << " " << edgep->fromp()
<< endl);
// Make the edge uncutable so we detect it in placement
edgep->cutable(false);
// Vertex::m_user begin: number indicates this edge was completed
// Try to assign ranks, presuming this edge is in place
// If we come across user()==placestep, we've detected a loop and must back out
bool loop
= placeIterate(static_cast<GraphAcycVertex*>(edgep->top()), edgep->fromp()->rank() + 1);
if (!loop) {
// No loop, we can keep it as uncutable
// Commit the new ranks we calculated
// Just cleanup the list. If this is slow, we can add another set of
// user counters to avoid cleaning up the list.
while (workBeginp()) workPop();
} else {
// Adding this edge would cause a loop, kill it
edgep->cutable(true); // So graph still looks pretty
cutOrigEdge(edgep, " Cut loop");
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
// Back out the ranks we calculated
while (GraphAcycVertex* vertexp = workBeginp()) {
workPop();
vertexp->rank(vertexp->m_storedRank);
}
}
}
bool GraphAcyc::placeIterate(GraphAcycVertex* vertexp, uint32_t currentRank) {
// Assign rank to each unvisited node
// rank() is the "committed rank" of the graph known without loops
// If larger rank is found, assign it and loop back through
// If we hit a back node make a list of all loops
if (vertexp->rank() >= currentRank) return false; // Already processed it
if (vertexp->user() == m_placeStep) return true; // Loop detected
vertexp->user(m_placeStep);
// Remember we're changing the rank of this node; might need to back out
if (!vertexp->m_onWorkList) {
vertexp->m_storedRank = vertexp->rank();
workPush(vertexp);
}
vertexp->rank(currentRank);
// Follow all edges and increase their ranks
for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
if (edgep->weight() && !edgep->cutable()) {
if (placeIterate(static_cast<GraphAcycVertex*>(edgep->top()), currentRank + 1)) {
// We don't need to reset user(); we'll use a different placeStep for the next edge
return true; // Loop detected
}
}
}
vertexp->user(0);
return false;
}
//----- Main algorithm entry point
void GraphAcyc::main() {
m_breakGraph.userClearEdges();
// Color based on possible loops
m_origGraphp->stronglyConnected(m_origEdgeFuncp);
// Make a new graph with vertices that have only a single vertex
// for each group of old vertices that are interconnected with unbreakable
// edges (and thus can't represent loops - if we did the unbreakable
// marking right, anyways)
buildGraph(m_origGraphp);
if (debug() >= 6) m_breakGraph.dumpDotFilePrefixed("acyc_pre");
// Perform simple optimizations before any cuttings
simplify(false);
if (debug() >= 5) m_breakGraph.dumpDotFilePrefixed("acyc_simp");
UINFO(4, " Cutting trivial loops\n");
simplify(true);
if (debug() >= 6) m_breakGraph.dumpDotFilePrefixed("acyc_mid");
UINFO(4, " Ranking\n");
m_breakGraph.rank(&V3GraphEdge::followNotCutable);
if (debug() >= 6) m_breakGraph.dumpDotFilePrefixed("acyc_rank");
UINFO(4, " Placement\n");
place();
if (debug() >= 6) m_breakGraph.dumpDotFilePrefixed("acyc_place");
UINFO(4, " Final Ranking\n");
// Only needed to assert there are no loops in completed graph
m_breakGraph.rank(&V3GraphEdge::followAlwaysTrue);
if (debug() >= 6) m_breakGraph.dumpDotFilePrefixed("acyc_done");
}
void V3Graph::acyclic(V3EdgeFuncP edgeFuncp) {
UINFO(4, "Acyclic\n");
GraphAcyc acyc(this, edgeFuncp);
acyc.main();
UINFO(4, "Acyclic done\n");
}