// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Graph acyclic algorithm
//
// Code available from: http://www.veripool.org/verilator
//
//*************************************************************************
//
// Copyright 2003-2014 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.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//*************************************************************************

#include "config_build.h"
#include "verilatedos.h"
#include <cstdio>
#include <cstdarg>
#include <unistd.h>
#include <algorithm>
#include <vector>
#include <list>

#include "V3Global.h"
#include "V3Graph.h"

//######################################################################
//######################################################################
// 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;		// Rank held until commit to edge placement
    bool		m_onWorkList;		// True if already on list of work to do
    bool		m_deleted;		// True if deleted
public:

    GraphAcycVertex(V3Graph* graphp, V3GraphVertex* origVertexp)
	: V3GraphVertex(graphp), m_origVertexp(origVertexp), m_onWorkList(false), m_deleted(false) {
    }
    virtual ~GraphAcycVertex() {}
    V3GraphVertex* origVertexp() const { return m_origVertexp; }
    void setDelete() { m_deleted = true; }
    bool isDelete() const { return m_deleted; }
    virtual string name() const { return m_origVertexp->name(); }
    virtual string dotColor() const { return m_origVertexp->dotColor(); }
};

//--------------------------------------------------------------------

class GraphAcycEdge : public V3GraphEdge {
    // userp() is always used to point to the head original graph edge
private:
    typedef list<V3GraphEdge*>	OrigEdgeList;	// List of orig edges, see also GraphAcyc's decl
    V3GraphEdge*	origEdgep() const {
	OrigEdgeList* oEListp = ((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() {}
    // yellow=we might still cut it, else oldEdge: yellowGreen=made uncutable, red=uncutable
    virtual string dotColor() const { return (cutable()?"yellow":origEdgep()->dotColor()); }
};

//--------------------------------------------------------------------

struct GraphAcycEdgeCmp {
    inline bool operator () (const V3GraphEdge* lhsp, const V3GraphEdge* rhsp) const {
	if (lhsp->weight() > rhsp->weight()) return 1;  // LHS goes first
	if (lhsp->weight() < rhsp->weight()) return 0;  // RHS goes first
	return 0;
    }
};

//--------------------------------------------------------------------

// CLASSES
class GraphAcyc {
private:
    typedef 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
    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;		// 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* vertexp);
    void simplifyOne (GraphAcycVertex* vertexp);
    void simplifyOut (GraphAcycVertex* vertexp);
    void simplifyDup (GraphAcycVertex* vertexp);
    void cutBasic (GraphAcycVertex* vertexp);
    void cutBackward (GraphAcycVertex* vertexp);
    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 NULL");
	if (!toEdgep->userp()) {
	    OrigEdgeList* oep = new OrigEdgeList;
	    m_origEdgeDelp.push_back(oep);
	    toEdgep->userp(oep);
	}
	OrigEdgeList* oEListp = (OrigEdgeList*)(toEdgep->userp());
	if (OrigEdgeList* addListp = (OrigEdgeList*)(addEdgep->userp())) {
	    for (OrigEdgeList::iterator it = addListp->begin(); it != addListp->end(); ++it) {
		oEListp->push_back(*it);
	    }
	    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 = (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 (OrigEdgeList::iterator it = oEListp->begin(); it != oEListp->end(); ++it) {
	    V3GraphEdge* origEdgep = *it;
	    origEdgep->cut();
	    UINFO(8,"  "<<why<<"   "<<origEdgep->fromp()<<" ->"<<origEdgep->top()<<endl);
	}
    }
    // Work Que
    void workPush(V3GraphVertex* vertexp) {
	GraphAcycVertex* avertexp = (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 (vector<OrigEdgeList*>::iterator it = m_origEdgeDelp.begin(); it != m_origEdgeDelp.end(); ++it) {
	    delete (*it);
	}
	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 = (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 = (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 = (GraphAcycVertex*)vertexp;
	if (avertexp->isDelete()) {
	    avertexp->unlinkDelete(&m_breakGraph); avertexp=NULL;
	}
    }
}

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);
	    edgep->unlinkDelete(); edgep = NULL;
	    workPush(otherVertexp);
	}
	while (V3GraphEdge* edgep = avertexp->inBeginp()) {
	    V3GraphVertex* otherVertexp = edgep->fromp();
	    //UINFO(9,"  in  "<<otherVertexp<<endl);
	    edgep->unlinkDelete(); edgep = NULL;
	    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);
	    edgeFromEdge(templateEdgep, inVertexp, outVertexp);
	    // Remove old edge
	    inEdgep->unlinkDelete(); inEdgep = NULL;
	    outEdgep->unlinkDelete(); outEdgep = NULL; templateEdgep=NULL;
	    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
		edgeFromEdge(inEdgep, inVertexp, outVertexp);
		// Remove old edge
		inEdgep->unlinkDelete(); inEdgep = NULL;
		workPush(inVertexp);
	    }
	    outEdgep->unlinkDelete(); outEdgep = NULL;
	    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(NULL);
    }
    // Mark edges and detect duplications
    for (V3GraphEdge* nextp, *edgep = avertexp->outBeginp(); edgep; edgep=nextp) {
	nextp = edgep->outNextp();
	V3GraphVertex* outVertexp = edgep->top();
	V3GraphEdge* prevEdgep = (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);
		edgep->unlinkDelete(); edgep = NULL;
	    } else if (!edgep->cutable()) {
		// !cutable duplicates prev  cutable: delete the earlier cutable
		UINFO(8,"    DelDupPrev "<<avertexp<<" -> "<<prevEdgep->top()<<endl);
		prevEdgep->unlinkDelete(); prevEdgep = NULL;
		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);
		edgep->unlinkDelete(); edgep = NULL;
	    }
	    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");
	    edgep->unlinkDelete(); edgep = NULL;
	    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");
	    edgep->unlinkDelete(); edgep = NULL;
	    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);

    vector<V3GraphEdge*>	edges;	// List of all edges to be processed
    edges.reserve(numEdges+1); // Make the vector properly sized right off the bat -- faster than reallocating
    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 (vector<V3GraphEdge*>::iterator it = edges.begin(); it!=edges.end(); ++it) {
	V3GraphEdge* edgep = (*it);
	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((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");
	edgep->unlinkDelete(); edgep = NULL;
	// Backout 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((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");
}
