diff --git a/src/V3Cfg.cpp b/src/V3Cfg.cpp index 54ec09ae8..e671cfd59 100644 --- a/src/V3Cfg.cpp +++ b/src/V3Cfg.cpp @@ -29,24 +29,9 @@ VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### -// ControlFlowGraph method definitions +// CfgBlock method definitions -bool ControlFlowGraph::containsLoop() const { - for (const V3GraphVertex& vtx : vertices()) { - const BasicBlock& current = static_cast(vtx); - for (const V3GraphEdge& edge : current.outEdges()) { - const BasicBlock& successor = *static_cast(edge.top()); - // IDs are the reverse post-order numbering, so easy to check for a back-edge - if (successor.id() < current.id()) return true; - } - } - return false; -} - -//###################################################################### -// BasicBlock method definitions - -std::string BasicBlock::name() const { +std::string CfgBlock::name() const { std::stringstream ss; ss << "BB " + std::to_string(id()) + ":\n"; for (AstNode* nodep : m_stmtps) { @@ -62,25 +47,25 @@ std::string BasicBlock::name() const { V3EmitV::debugVerilogForTree(nodep, ss); } } - std::string text = VString::replaceSubstr(ss.str(), "\n", "\\l "); - if (inEmpty()) text = "**ENTER**\n" + text; - if (outEmpty()) text = text + "\n**EXIT**"; + std::string text = VString::replaceSubstr( + VString::replaceSubstr(ss.str(), "\n", "\\l "), "\"", "\\\""); + if (isEnter()) text = "**ENTER**\n" + text; + if (isExit()) text = text + "\n**EXIT**"; return text; } -std::string BasicBlock::dotShape() const { return "rect"; } -std::string BasicBlock::dotRank() const { - if (inEmpty()) return "source"; - if (outEmpty()) return "sink"; +std::string CfgBlock::dotShape() const { return "rect"; } +std::string CfgBlock::dotRank() const { + if (isEnter()) return "source"; + if (isExit()) return "sink"; return ""; } //###################################################################### -// ControlFlowEdge method definitions +// CfgEdge method definitions -std::string ControlFlowEdge::dotLabel() const { +std::string CfgEdge::dotLabel() const { std::string label = "E" + std::to_string(id()); - const BasicBlock& source = *fromp()->as(); - const ControlFlowEdge* const untknp = source.untknEdgep(); + const CfgEdge* const untknp = srcp()->untknEdgep(); if (this == untknp) { label += " / F"; } else if (untknp) { @@ -88,3 +73,249 @@ std::string ControlFlowEdge::dotLabel() const { } return label; } + +//###################################################################### +// CfgGraph method definitions + +static void cfgOrderVisitBlock(std::vector& postOrderEnumeration, CfgBlock* bbp) { + // Mark visited + bbp->user(1); + // Visit un-visited successors + if (CfgBlock* const takenp = bbp->takenp()) { + if (!takenp->user()) cfgOrderVisitBlock(postOrderEnumeration, takenp); + if (CfgBlock* const untknp = bbp->untknp()) { + if (!untknp->user()) cfgOrderVisitBlock(postOrderEnumeration, untknp); + } + } + // Add to post order enumeration + postOrderEnumeration.emplace_back(bbp); +}; + +void CfgGraph::rpoBlocks() { + UASSERT_OBJ(m_nEdits != m_nLastOrdered, m_enterp, "Redundant 'CfgGraph::order' call"); + m_nLastOrdered = m_nEdits; + + // Reset marks + for (V3GraphVertex& v : vertices()) v.user(0); + + // Compute post-order enumeration. Simple recursive algorith will do. + std::vector postOrderEnumeration; + postOrderEnumeration.reserve(m_nBlocks); + cfgOrderVisitBlock(postOrderEnumeration, m_enterp); + UASSERT_OBJ(postOrderEnumeration.size() == m_nBlocks, m_enterp, "Inconsistent block count"); + + // Assign block IDs equal to the reverse post-order number and sort vertices + for (size_t i = 0; i < postOrderEnumeration.size(); ++i) { + CfgBlock* const bbp = postOrderEnumeration[m_nBlocks - 1 - i]; + bbp->m_rpoNumber = i; + vertices().unlink(bbp); + vertices().linkBack(bbp); + } + + // Assign edge IDs + size_t edgeCount = 0; + for (V3GraphVertex& v : vertices()) { + for (V3GraphEdge& e : v.outEdges()) static_cast(e).m_id = edgeCount++; + } + UASSERT_OBJ(edgeCount == m_nEdges, m_enterp, "Inconsistent edge count"); +} + +bool CfgGraph::containsLoop() const { + for (const V3GraphVertex& vtx : vertices()) { + const CfgBlock& current = static_cast(vtx); + for (const V3GraphEdge& edge : current.outEdges()) { + const CfgBlock& successor = *static_cast(edge.top()); + // IDs are the reverse post-order numbering, so easy to check for a back-edge + if (successor.id() < current.id()) return true; + } + } + return false; +} + +void CfgGraph::minimize() { + // Remove empty blocks (except enter and exit) + for (V3GraphVertex* const vtxp : vertices().unlinkable()) { + CfgBlock* const bbp = static_cast(vtxp); + if (bbp->isEnter()) continue; + if (bbp->isExit()) continue; + if (!bbp->stmtps().empty()) continue; + UASSERT(!bbp->isBranch(), "Empty block should have a single successor"); + CfgBlock* const succp = bbp->takenp(); + for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp); + ++m_nEdits; + --m_nEdges; + --m_nBlocks; + VL_DO_DANGLING(bbp->unlinkDelete(this), bbp); + } + + // Combine sequential blocks + for (V3GraphVertex* const vtxp : vertices().unlinkable()) { + CfgBlock* const srcp = static_cast(vtxp); + if (srcp->isExit()) continue; + if (srcp->isBranch()) continue; + CfgBlock* const dstp = srcp->takenp(); + if (dstp->isJoin()) continue; + // Combine them + if (srcp->isEnter()) m_enterp = dstp; + std::vector stmtps{std::move(srcp->m_stmtps)}; + stmtps.reserve(stmtps.size() + dstp->m_stmtps.size()); + stmtps.insert(stmtps.end(), dstp->m_stmtps.begin(), dstp->m_stmtps.end()); + dstp->m_stmtps = std::move(stmtps); + for (V3GraphEdge* const edgep : srcp->inEdges().unlinkable()) edgep->relinkTop(dstp); + ++m_nEdits; + --m_nEdges; + --m_nBlocks; + VL_DO_DANGLING(srcp->unlinkDelete(this), srcp); + } + + if (m_nEdits != m_nLastOrdered) rpoBlocks(); + if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-minimize"); +} + +void CfgGraph::breakCriticalEdges() { + // Gather critical edges + std::vector criticalEdges; + criticalEdges.reserve(m_nEdges); + for (V3GraphVertex& vtx : vertices()) { + const CfgBlock& bb = static_cast(vtx); + if (!bb.isBranch()) continue; + for (V3GraphEdge& edge : vtx.outEdges()) { + const CfgBlock& succ = static_cast(*edge.top()); + if (!succ.isJoin()) continue; + criticalEdges.emplace_back(static_cast(&edge)); + } + } + // Insert blocks + for (CfgEdge* const edgep : criticalEdges) { + CfgBlock* const newp = addBlock(); + addTakenEdge(newp, edgep->dstp()); + edgep->relinkTop(newp); + } + + if (m_nEdits != m_nLastOrdered) rpoBlocks(); + if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-breakCriticalEdges"); +} + +// Given a branching basic block, if the sub-graph below this branch, up until +// the point where all of its control flow path convertes is series-parallel, +// then return the (potentially newly created) basic block with exactly 2 +// predecessors where the two control flow paths from this branch have joined. +// If the relevant sub-graph is not series-parallel (there is a control flow +// path between the branches, or to a path not dominated by the given branch), +// then return nullptr. Cached results in the given map +CfgBlock* CfgGraph::getOrCreateTwoWayJoinFor(CfgBlock* bbp) { + UASSERT_OBJ(bbp->isBranch(), bbp, "Not a branch"); + + // Mark visited + UASSERT_OBJ(!bbp->user(), bbp, "Should not visit twice"); + bbp->user(1); + + // We need the edge converting to a join block along both path. This is how we find it: + const auto chaseEdge = [&](CfgEdge* edgep) -> CfgEdge* { + while (true) { + CfgBlock* dstp = edgep->dstp(); + // Stop if found the joining block along this path + if (dstp->isJoin()) return edgep; + // If the successor is a branch, recursively get it's 2-way join block + while (dstp->isBranch()) { + dstp = getOrCreateTwoWayJoinFor(dstp); + // If the subgarph below dstp is not series-parallel, then no solution + if (!dstp) return nullptr; + } + UASSERT_OBJ(!dstp->isExit(), bbp, "Non-convergent branch - multiple Exit blocks?"); + edgep = dstp->takenEdgep(); + } + }; + + // Walk down both paths + CfgEdge* const takenEdgep = chaseEdge(bbp->takenEdgep()); + if (!takenEdgep) return nullptr; + CfgEdge* const untknEdgep = chaseEdge(bbp->untknEdgep()); + if (!untknEdgep) return nullptr; + // If we ended up at different joining blocks, then there is a path from one + // of the branches into a path of another branch before 'bbp', no solution + if (takenEdgep->dstp() != untknEdgep->dstp()) return nullptr; + + // Pick up the common successor + CfgBlock* const succp = takenEdgep->dstp(); + // If the common successor is a 2-way join, we can use it directly + if (succp->isTwoWayJoin()) return succp; + // Otherwise insert a new block to join the 2 paths of the original block + CfgBlock* const joinp = addBlock(); + addTakenEdge(joinp, succp); + takenEdgep->relinkTop(joinp); + untknEdgep->relinkTop(joinp); + return joinp; +} + +bool CfgGraph::insertTwoWayJoins() { + // Reset marks + for (V3GraphVertex& v : vertices()) v.user(0); + + bool isSeriesParallel = true; + + // We will be adding vertices at the end. That's OK, they don't need to be visited again + for (V3GraphVertex& v : vertices()) { + CfgBlock& bb = static_cast(v); + // Skip if already visited + if (bb.user()) continue; + // Skip if not a branch + if (!bb.isBranch()) continue; + // Fix it up, record if failed + if (!getOrCreateTwoWayJoinFor(&bb)) isSeriesParallel = false; + } + + if (m_nEdits != m_nLastOrdered) rpoBlocks(); + if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-insertTwoWayJoins"); + + return isSeriesParallel; +} + +//###################################################################### +// CfgDominatorTree + +const CfgBlock* CfgDominatorTree::intersect(const CfgBlock* ap, const CfgBlock* bp) { + while (ap != bp) { + while (*ap > *bp) ap = m_bb2Idom[*ap]; + while (*bp > *ap) bp = m_bb2Idom[*bp]; + } + return ap; +} + +CfgDominatorTree::CfgDominatorTree(const CfgGraph& cfg) + : m_bb2Idom{cfg.makeBlockMap()} { + + // Build the immediate dominator map, using algorithm from: + // "A Simple, Fast Dominance Algorithm", Keith D. Cooper et al., 2006 + // Immediate dominator of the enter block + + // Point enteer block to itself, while computing below + m_bb2Idom[cfg.enter()] = &cfg.enter(); + // Iterate until settled + for (bool changed = true; changed;) { + changed = false; + // For each vertex except enter block + for (const V3GraphVertex& vtx : cfg.vertices()) { + const CfgBlock& curr = static_cast(vtx); + if (curr.isEnter()) continue; // Skip entry block + + // For each predecessor of current block + const CfgBlock* idom = nullptr; + for (const V3GraphEdge& edge : curr.inEdges()) { + const CfgBlock& pred = static_cast(*edge.fromp()); + // Skip if perdecessor not yet processed + if (!m_bb2Idom[pred]) continue; + // Pick first, then use intersect + idom = !idom ? &pred : intersect(&pred, idom); + } + + // If chenged, record it, else move on + if (idom == m_bb2Idom[curr]) continue; + m_bb2Idom[curr] = idom; + changed = true; + } + } + + // The enter block is the root of the tree and does not itself have an immediate dominator + m_bb2Idom[cfg.enter()] = nullptr; +} diff --git a/src/V3Cfg.h b/src/V3Cfg.h index 2eba9514e..51c426659 100644 --- a/src/V3Cfg.h +++ b/src/V3Cfg.h @@ -68,69 +68,103 @@ #include //###################################################################### -// DenseMap - This can be made generic if needed +// Control Flow Graph (CFG) and related data structures -// Map from unique non-negative integers (IDs) to generic values. As opposed -// to a std::map/std::unordered_map, in DenseMap, all entries are value -// initialized at construction, and the map is always dense. This can be -// improved if necessary but is sufficient for our current purposes. -template -class DenseMap final { - std::vector m_map; // The map, stored as a vector +class CfgGraph; +class CfgBlock; +class CfgEdge; -public: - // CONSTRUCTOR - explicit DenseMap(size_t size) - : m_map{size} {} - - T_Value& operator[](const T_Key& key) { return m_map.at((key.*Index)()); } - const T_Value& operator[](const T_Key& key) const { return m_map.at((key.*Index)()); } -}; +template +class CfgMap; +template +class CfgBlockMap; +template +class CfgEdgeMap; //###################################################################### -// ControlFlowGraph data structure +// CfgBlock - A basic block (verticies of the control flow graph) -class ControlFlowGraph; -class BasicBlock; -class ControlFlowEdge; - -// A basic block (verticies of the control flow graph) -class BasicBlock final : public V3GraphVertex { - VL_RTTI_IMPL(BasicBlock, V3GraphVertex) +class CfgBlock final : public V3GraphVertex { + friend class CfgGraph; + template + friend class CfgMap; friend class CfgBuilder; - // STATE - Immutable after construction, set by CfgBuilder - // V3GraphEdge::user() is set to the unique id by CfgBuilder - std::vector m_stmtps; // statements in this BasicBlock + // STATE + CfgGraph* const m_cfgp; // The control flow graph this CfgBlock is under + size_t m_rpoNumber; // Reverse post-order number and unique ID of this CfgBlock - // CONSTRUCTOR - via CfgBuilder only - inline explicit BasicBlock(ControlFlowGraph* graphp); - ~BasicBlock() override = default; + // V3GraphEdge::user() is set to the unique id by CfgBuilder + std::vector m_stmtps; // statements in this CfgBlock + + // PRIVATE METHODS + // ID (reverse post-order numpber) of this block + inline size_t id(); + inline size_t id() const; + + // CONSTRUCTOR/DESTRUCTOR - via CfgGraph only + inline explicit CfgBlock(CfgGraph* cfgp); + ~CfgBlock() override = default; public: - // METHODS - all const + // PUBLIC METHODS - // Statements in this BasicBlock + // Is this the entry block of the CFG? + bool isEnter() const { return inEmpty(); } + // Is this the exit block of the CFG? + bool isExit() const { return outEmpty(); } + // Is this a branching block (multiple successors)? + bool isBranch() const { return outEdges().hasMultipleElements(); } + // Is this a control flow convergence block (multiple predecessors)? + bool isJoin() const { return inEdges().hasMultipleElements(); } + // Is this a join of exactly 2 paths? + bool isTwoWayJoin() const { return inEdges().hasTwoElements(); } + + // The edge going to the taken (or uncinditional) successor, or nullptr if exit block + inline CfgEdge* takenEdgep(); + inline const CfgEdge* takenEdgep() const; + // The edge going to the untaken successor, or nullptr if not a branch, or exit block + inline CfgEdge* untknEdgep(); + inline const CfgEdge* untknEdgep() const; + // The taken successor block, or nullptr if exit block + inline CfgBlock* takenp(); + inline const CfgBlock* takenp() const; + // The untakens successor block, or nullptr if not a branch, or exit block + inline CfgBlock* untknp(); + inline const CfgBlock* untknp() const; + + // The first predecessor edge, or nullptr if enter block + inline CfgEdge* firstPredecessorEdgep(); + inline const CfgEdge* firstPredecessorEdgep() const; + // The last predecessor edge, or nullptr if enter block + inline CfgEdge* lastPredecessorEdgep(); + inline const CfgEdge* lastPredecessorEdgep() const; + // The first predecessor block, or nullptr if enter block + inline CfgBlock* firstPredecessorp(); + inline const CfgBlock* firstPredecessorp() const; + // The last predecessor block, or nullptr if enter block + inline CfgBlock* lastPredecessorp(); + inline const CfgBlock* lastPredecessorp() const; + + // Statements in this CfgBlock const std::vector& stmtps() const { return m_stmtps; } - // Unique ID of this BasicBlock - defines topological ordering - size_t id() const { return V3GraphVertex::user(); } - // The edge corresponding to the terminator branch being taken (including unonditoinal goto) - inline const ControlFlowEdge* takenEdgep() const; - // The edge corresponding to the terminator branch being not taken (or nullptr if goto) - inline const ControlFlowEdge* untknEdgep() const; - // Same as takenpEdgep/untknEdgep but returns the successor basic blocks - inline const BasicBlock* takenSuccessorp() const; - inline const BasicBlock* untknSuccessorp() const; + // Ordering is done on reverese post-order numbering. For loop-free graph + // this ensures that a block that compares less than another is not a + // successor of the other block (it is an ancestor, or sibling). + bool operator<(const CfgBlock& that) const { return id() < that.id(); } + bool operator>(const CfgBlock& that) const { return id() > that.id(); } + bool operator==(const CfgBlock& that) const { return this == &that; } - void forEachSuccessor(std::function f) const { - for (const V3GraphEdge& edge : outEdges()) f(*edge.top()->as()); - } - - void forEachPredecessor(std::function f) const { - for (const V3GraphEdge& edge : inEdges()) f(*edge.fromp()->as()); + // Iterators + void forEachSuccessor(std::function f) const { + for (const V3GraphEdge& edge : outEdges()) f(*static_cast(edge.top())); + } + void forEachPredecessor(std::function f) const { + for (const V3GraphEdge& edge : inEdges()) f(*static_cast(edge.fromp())); } + // Source location for debugging FileLine* fileline() const override { return !m_stmtps.empty() ? m_stmtps.front()->fileline() : nullptr; } @@ -141,130 +175,397 @@ public: std::string dotRank() const override; }; -// A control flow graph edge -class ControlFlowEdge final : public V3GraphEdge { - VL_RTTI_IMPL(ControlFlowEdge, V3GraphEdge) - friend class CfgBuilder; +//###################################################################### +// CfgEdge - An edges of the control flow graph + +class CfgEdge final : public V3GraphEdge { + friend class CfgGraph; + template + friend class CfgMap; // STATE - Immutable after construction, set by CfgBuilder - // V3GraphEdge::user() is set to the unique id by CfgBuilder + CfgGraph* const m_cfgp; // The control flow graph this CfgEdge is under + size_t m_id; // Unique ID of this vertex - // CONSTRUCTOR - via CfgBuilder only - inline ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp); - ~ControlFlowEdge() override = default; + // PRIVATE METHODS + // Unique ID of this CfgEdge - no particular meaning + inline size_t id(); + inline size_t id() const; + + // CONSTRUCTOR/DESTRUCTOR - via CfgGraph only + inline CfgEdge(CfgGraph* graphp, CfgBlock* srcp, CfgBlock* dstp); + ~CfgEdge() override = default; public: // METHODS - all const - // Unique ID of this ControlFlowEdge - no particular meaning - size_t id() const { return user(); } - - // Source/destination BasicBlock - const BasicBlock& src() const { return *static_cast(fromp()); } - const BasicBlock& dst() const { return *static_cast(top()); } + // Source/destination CfgBlock + const CfgBlock* srcp() const { return static_cast(fromp()); } + const CfgBlock* dstp() const { return static_cast(top()); } + CfgBlock* srcp() { return static_cast(fromp()); } + CfgBlock* dstp() { return static_cast(top()); } // For Graphviz dumps only std::string dotLabel() const override; }; -template -using BasicBlockMap = DenseMap; +//###################################################################### +// CfgGraph - The control flow graph -template -using ControlFlowEdgeMap = DenseMap; - -// The control flow graph -class ControlFlowGraph final : public V3Graph { +class CfgGraph final : public V3Graph { + friend class CfgBlock; + friend class CfgEdge; + template + friend class CfgMap; friend class CfgBuilder; - // STATE - Immutable after construction, set by CfgBuilder - BasicBlock* m_enterp = nullptr; // The singular entry vertex - BasicBlock* m_exitp = nullptr; // The singular exit vertex - size_t m_nBasicBlocks = 0; // Number of BasicBlocks in this ControlFlowGraph - size_t m_nEdges = 0; // Number of ControlFlowEdges in this ControlFlowGraph + // STATE + size_t m_nEdits = 0; // Edit count of this graph + size_t m_nLastOrdered = 0; // Last edit count blocks were ordered + CfgBlock* m_enterp = nullptr; // The singular entry vertex + CfgBlock* m_exitp = nullptr; // The singular exit vertex + size_t m_nBlocks = 0; // Number of CfgBlocks in this CfgGraph + size_t m_nEdges = 0; // Number of CfgEdges in this CfgGraph - // CONSTRUCTOR - via CfgBuilder only - ControlFlowGraph() = default; + // PRIVATE METHODS -public: - ~ControlFlowGraph() override = default; + // Compute reverse post-order enumeration of blocks, and sort them + // accordingly. Assign blocks, and edge IDs. Invalidates all previous IDs. + void rpoBlocks(); - // METHODS - void foreach(std::function f) const { - for (const V3GraphVertex& vtx : vertices()) f(*vtx.as()); + // Add a new CfgBlock to this graph + CfgBlock* addBlock() { + ++m_nEdits; + ++m_nBlocks; + return new CfgBlock{this}; } + // Add a new taken (or unconditional) CfgEdge to this CFG + void addTakenEdge(CfgBlock* srcp, CfgBlock* dstp) { + UASSERT_OBJ(srcp->m_cfgp == this, srcp, "'srcp' is not in this graph"); + UASSERT_OBJ(dstp->m_cfgp == this, dstp, "'dstp' is not in this graph"); + UASSERT_OBJ(!srcp->takenEdgep(), srcp, "Taken edge should be added first"); + // + UASSERT_OBJ(dstp != m_enterp, dstp, "Enter block cannot have a predecessor"); + UASSERT_OBJ(srcp != m_exitp, srcp, "Exit block cannot have a successor"); + ++m_nEdits; + ++m_nEdges; + new CfgEdge{this, srcp, dstp}; + } + + // Add a new untaken CfgEdge to this CFG + void addUntknEdge(CfgBlock* srcp, CfgBlock* dstp) { + UASSERT_OBJ(srcp->m_cfgp == this, srcp, "'srcp' is not in this graph"); + UASSERT_OBJ(dstp->m_cfgp == this, dstp, "'dstp' is not in this graph"); + UASSERT_OBJ(srcp->takenEdgep(), srcp, "Untaken edge shold be added second"); + UASSERT_OBJ(srcp->takenp() != dstp, srcp, "Untaken branch targets the same block"); + // + UASSERT_OBJ(dstp != m_enterp, dstp, "Enter block cannot have a predecessor"); + UASSERT_OBJ(srcp != m_exitp, srcp, "Exit block cannot have a successor"); + ++m_nEdits; + ++m_nEdges; + new CfgEdge{this, srcp, dstp}; + } + + size_t idOf(const CfgBlock* bbp) const { + UASSERT_OBJ(m_nEdits == m_nLastOrdered, m_enterp, "Cfg was edited but not re-ordered"); + return bbp->m_rpoNumber; + } + + size_t idOf(const CfgEdge* edgep) const { + UASSERT_OBJ(m_nEdits == m_nLastOrdered, m_enterp, "Cfg was edited but not re-ordered"); + return edgep->m_id; + } + + // Implementation for insertTwoWayJoins + CfgBlock* getOrCreateTwoWayJoinFor(CfgBlock* bbp); + + // CONSTRUCTOR - use CfgGraph::build, which might fail, so this can't be public + CfgGraph() = default; + +public: + ~CfgGraph() override = default; + + // STATIC FUNCTIONS + + // Build CFG for the given list of statements + static std::unique_ptr build(AstNode* stmtsp); + + // PUBLIC METHODS + // Accessors - const BasicBlock& enter() const { return *m_enterp; } - const BasicBlock& exit() const { return *m_exitp; } + const CfgBlock& enter() const { return *m_enterp; } + const CfgBlock& exit() const { return *m_exitp; } // Number of basic blocks in this graph - size_t nBasicBlocks() const { return m_nBasicBlocks; } + size_t nBlocks() const { return m_nBlocks; } // Number of control flow edges in this graph size_t nEdges() const { return m_nEdges; } - // Create a BasicBlock map for this graph + // Create a CfgBlock map for this graph template - BasicBlockMap makeBasicBlockMap() const { - return BasicBlockMap{nBasicBlocks()}; - } - // Create a ControlFlowEdgeMap map for this graph + inline CfgBlockMap makeBlockMap() const; + // Create a CfgEdgeMap map for this graph template - ControlFlowEdgeMap makeEdgeMap() const { - return ControlFlowEdgeMap{nEdges()}; - } + inline CfgEdgeMap makeEdgeMap() const; // Returns true iff the graph contains a loop (back-edge) bool containsLoop() const; + + //------------------------------------------------------------------ + // The following methods mutate this CFG and invalidate CfgBlock and + // CfgEdge IDs and associated CfgBlockMap, CfgEdgeMap and other + // query instances. + + // Remove empty blocks, combine sequential blocks. Keeps the enter/exit block, even if empty. + void minimize(); + + // Insert empty blocks to fix critical edges (edges that have a source with + // multiple successors, and a destination with multiple predecessors) + void breakCriticalEdges(); + + bool insertTwoWayJoins(); }; //###################################################################### -// Inline method definitions +// CfgMap - Map from CfgBlock or CfgEdge to generic values -BasicBlock::BasicBlock(ControlFlowGraph* cfgp) - : V3GraphVertex{cfgp} {} +template +class CfgMap VL_NOT_FINAL { + // As opposed to a std::map/std::unordered_map, all entries are value + // initialized at construction, and the map is always dense. This can + // be improved if necessary but is sufficient for our current purposes. -const ControlFlowEdge* BasicBlock::takenEdgep() const { - // It's always the first edge - const V3GraphEdge* const frontp = outEdges().frontp(); - return static_cast(frontp); -} + const CfgGraph* m_cfgp; // The control flow graph this map is for + size_t m_created; // Edit count of CFG this map was created at + std::vector m_map; // The map, stored as a vector -const ControlFlowEdge* BasicBlock::untknEdgep() const { - // It's always the second (last) edge - const V3GraphEdge* const frontp = outEdges().frontp(); - const V3GraphEdge* const backp = outEdges().backp(); - return backp != frontp ? static_cast(backp) : nullptr; -} +protected: + // CONSTRUCTOR + explicit CfgMap(const CfgGraph* cfgp, size_t size) + : m_cfgp{cfgp} + , m_created{cfgp->m_nEdits} + , m_map{size} {} -const BasicBlock* BasicBlock::takenSuccessorp() const { - const ControlFlowEdge* const edgep = takenEdgep(); - return edgep ? static_cast(edgep->top()) : nullptr; -} +public: + // Can create an empty map + CfgMap() + : m_cfgp{nullptr} + , m_created{0} {} -const BasicBlock* BasicBlock::untknSuccessorp() const { - const ControlFlowEdge* const edgep = untknEdgep(); - return edgep ? static_cast(edgep->top()) : nullptr; -} + // Copyable, movable + CfgMap(const CfgMap&) = default; + CfgMap(CfgMap&&) = default; + CfgMap& operator=(const CfgMap&) = default; + CfgMap& operator=(CfgMap&&) = default; -ControlFlowEdge::ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp) - : V3GraphEdge{graphp, srcp, dstp, 1, false} {} + T_Value& operator[](const T_Key& key) { + UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale"); + UASSERT_OBJ(m_cfgp == key.m_cfgp, m_cfgp->m_enterp, "Key not in this CFG"); + return m_map.at(key.id()); + } + const T_Value& operator[](const T_Key& key) const { + UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale"); + UASSERT_OBJ(m_cfgp == key.m_cfgp, m_cfgp->m_enterp, "Key not in this CFG"); + return m_map.at(key.id()); + } + T_Value& operator[](const T_Key* keyp) { + UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale"); + UASSERT_OBJ(m_cfgp == keyp->m_cfgp, m_cfgp->m_enterp, "Key not in this CFG"); + return m_map.at(keyp->id()); + } + const T_Value& operator[](const T_Key* keyp) const { + UASSERT_OBJ(m_created == m_cfgp->m_nEdits, m_cfgp->m_enterp, "Map is stale"); + UASSERT_OBJ(m_cfgp == keyp->m_cfgp, m_cfgp->m_enterp, "Key not in this CFG"); + return m_map.at(keyp->id()); + } +}; + +template +class CfgBlockMap final : public CfgMap { + friend class CfgGraph; + // CONSTRUCTOR - Create one via CfgGraph::makeBlockMap + explicit CfgBlockMap(const CfgGraph* cfgp) + : CfgMap{cfgp, cfgp->nBlocks()} {} + +public: + // Can create an empty map + CfgBlockMap() = default; + // Copyable, movable + CfgBlockMap(const CfgBlockMap&) = default; + CfgBlockMap(CfgBlockMap&&) = default; + CfgBlockMap& operator=(const CfgBlockMap&) = default; + CfgBlockMap& operator=(CfgBlockMap&&) = default; +}; + +template +class CfgEdgeMap final : public CfgMap { + friend class CfgGraph; + // CONSTRUCTOR - Create one via CfgGraph::makeEdgeMap + explicit CfgEdgeMap(const CfgGraph* cfgp) + : CfgMap{cfgp, cfgp->nEdges()} {} + +public: + // Can create an empty map + CfgEdgeMap() = default; + // Copyable, movable + CfgEdgeMap(const CfgEdgeMap&) = default; + CfgEdgeMap(CfgEdgeMap&&) = default; + CfgEdgeMap& operator=(const CfgEdgeMap&) = default; + CfgEdgeMap& operator=(CfgEdgeMap&&) = default; +}; //###################################################################### -// ControlFlowGraph functions +// Innline method definitions + +// --- CfgBlock --- + +CfgBlock::CfgBlock(CfgGraph* cfgp) + : V3GraphVertex{cfgp} + , m_cfgp{cfgp} {} + +size_t CfgBlock::id() { return m_cfgp->idOf(this); } +size_t CfgBlock::id() const { return m_cfgp->idOf(this); } + +// Successor edges +CfgEdge* CfgBlock::takenEdgep() { // It's always the first edge + return isExit() ? nullptr : static_cast(outEdges().frontp()); +} +const CfgEdge* CfgBlock::takenEdgep() const { // It's always the first edge + return isExit() ? nullptr : static_cast(outEdges().frontp()); +} +CfgEdge* CfgBlock::untknEdgep() { // It's always the second (last) edge + return isBranch() ? static_cast(outEdges().backp()) : nullptr; +} +const CfgEdge* CfgBlock::untknEdgep() const { // It's always the second (last) edge + return isBranch() ? static_cast(outEdges().backp()) : nullptr; +} +CfgBlock* CfgBlock::takenp() { + return isExit() ? nullptr : static_cast(outEdges().frontp()->top()); +} +const CfgBlock* CfgBlock::takenp() const { + return isExit() ? nullptr : static_cast(outEdges().frontp()->top()); +} +CfgBlock* CfgBlock::untknp() { + return isBranch() ? static_cast(outEdges().backp()->top()) : nullptr; +} +const CfgBlock* CfgBlock::untknp() const { + return isBranch() ? static_cast(outEdges().backp()->top()) : nullptr; +} + +// Predecessor edges +CfgEdge* CfgBlock::firstPredecessorEdgep() { // + return static_cast(inEdges().frontp()); +} +const CfgEdge* CfgBlock::firstPredecessorEdgep() const { // + return static_cast(inEdges().frontp()); +} +CfgEdge* CfgBlock::lastPredecessorEdgep() { // + return static_cast(inEdges().backp()); +} +const CfgEdge* CfgBlock::lastPredecessorEdgep() const { // + return static_cast(inEdges().backp()); +} +CfgBlock* CfgBlock::firstPredecessorp() { // + return isEnter() ? nullptr : firstPredecessorEdgep()->srcp(); +} +const CfgBlock* CfgBlock::firstPredecessorp() const { // + return isEnter() ? nullptr : firstPredecessorEdgep()->srcp(); +} +CfgBlock* CfgBlock::lastPredecessorp() { // + return isEnter() ? nullptr : lastPredecessorEdgep()->srcp(); +} +const CfgBlock* CfgBlock::lastPredecessorp() const { // + return isEnter() ? nullptr : lastPredecessorEdgep()->srcp(); +} + +// --- CfgEdge --- + +CfgEdge::CfgEdge(CfgGraph* cfgp, CfgBlock* srcp, CfgBlock* dstp) + : V3GraphEdge{cfgp, srcp, dstp, 1, false} + , m_cfgp{cfgp} {} + +size_t CfgEdge::id() { return m_cfgp->idOf(this); } +size_t CfgEdge::id() const { return m_cfgp->idOf(this); } + +// --- CfgGraph --- + +template +CfgBlockMap CfgGraph::makeBlockMap() const { + return CfgBlockMap{this}; +} +template +CfgEdgeMap CfgGraph::makeEdgeMap() const { + return CfgEdgeMap{this}; +} + +//###################################################################### +// CfgDominatorTree + +class CfgDominatorTree final { + // STATE + CfgBlockMap m_bb2Idom; // Map from CfgBlock to its immediate dominator + + // PRIVATE METHODS + + // Part of algorithm to compute m_bb2Idom, see consructor + const CfgBlock* intersect(const CfgBlock* ap, const CfgBlock* bp); + +public: + // CONSTRUCTOR + explicit CfgDominatorTree(const CfgGraph& cfg); + // Can create an empty map + CfgDominatorTree() = default; + // Copyable, movable + CfgDominatorTree(const CfgDominatorTree&) = default; + CfgDominatorTree(CfgDominatorTree&&) = default; + CfgDominatorTree& operator=(const CfgDominatorTree&) = default; + CfgDominatorTree& operator=(CfgDominatorTree&&) = default; + + // PUBLIC METHODS + + // Return unique CfgBlock that dominates both of the given blocks, but does + // not strictly dominate any other block that dominates both blocks. It + // will return 'ap' or 'bp' if one dominates the other (or are the same) + const CfgBlock* closestCommonDominator(const CfgBlock* ap, const CfgBlock* bp) const { + while (ap != bp) { + if (*ap < *bp) { + bp = m_bb2Idom[bp]; + } else { + ap = m_bb2Idom[ap]; + } + } + return ap; + } + + // Returns true if 'ap' dominates 'bp' + bool dominates(const CfgBlock* const ap, const CfgBlock* bp) { + // Walk up the dominator tree from 'bp' until reaching 'ap' or the root + while (true) { + // True if 'ap' is above (or same as) 'bp' + if (ap == bp) return true; + // Step up the dominator tree + bp = m_bb2Idom[bp]; + // False if reached the root + if (!bp) return false; + } + } +}; + +//###################################################################### +// V3Cfg namespace V3Cfg { -// Build control flow graph for given node -std::unique_ptr build(const AstNodeProcedure*); // Compute AstVars live on entry to given CFG. That is, variables that might // be read before wholly assigned in the CFG. Returns nullptr if the analysis // failed due to unhandled statements or data types involved in the CFG. // On success, returns a vector of AstVar or AstVarScope nodes live on entry. -std::unique_ptr> liveVars(const ControlFlowGraph&); +std::unique_ptr> liveVars(const CfgGraph&); // Same as liveVars, but return AstVarScopes insted -std::unique_ptr> liveVarScopes(const ControlFlowGraph&); +std::unique_ptr> liveVarScopes(const CfgGraph&); + } //namespace V3Cfg #endif // VERILATOR_V3CFG_H_ diff --git a/src/V3CfgBuilder.cpp b/src/V3CfgBuilder.cpp index a4b315b99..407c0aba3 100644 --- a/src/V3CfgBuilder.cpp +++ b/src/V3CfgBuilder.cpp @@ -31,35 +31,18 @@ VL_DEFINE_DEBUG_FUNCTIONS; -class CfgBuilder final : VNVisitorConst { +class CfgBuilder final : public VNVisitorConst { // STATE // The graph being built, or nullptr if failed to build one - std::unique_ptr m_cfgp{new ControlFlowGraph}; + std::unique_ptr m_cfgp{new CfgGraph}; // Current basic block to add statements to - BasicBlock* m_currBBp = nullptr; + CfgBlock* m_currBBp = nullptr; // Continuation block for given JumpBlock - std::unordered_map m_jumpBlockContp; + std::unordered_map m_jumpBlockContp; // METHODS - // Create new Basicblock block in the CFG - BasicBlock& addBasicBlock() { return *new BasicBlock{m_cfgp.get()}; } - - // Create new taken (or unconditional) ControlFlowEdge in the CFG - void addTakenEdge(BasicBlock& src, BasicBlock& dst) { - UASSERT_OBJ(src.outEmpty(), &src, "Taken edge should be added first"); - new ControlFlowEdge{m_cfgp.get(), &src, &dst}; - } - - // Create new untaken ControlFlowEdge in the CFG - void addUntknEdge(BasicBlock& src, BasicBlock& dst) { - UASSERT_OBJ(src.outSize1(), &src, "Untaken edge shold be added second"); - UASSERT_OBJ(src.outEdges().frontp()->top() != &dst, &src, - "Untaken branch targets the same block as the taken branch"); - new ControlFlowEdge{m_cfgp.get(), &src, &dst}; - } - - // Add the given statement to the current BasicBlock + // Add the given statement to the current CfgBlock void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); } // Used to handle statements not representable in the CFG @@ -115,71 +98,71 @@ class CfgBuilder final : VNVisitorConst { addStmt(nodep); // Create then/else/continuation blocks - BasicBlock& thenBB = addBasicBlock(); - BasicBlock& elseBB = addBasicBlock(); - BasicBlock& contBB = addBasicBlock(); - addTakenEdge(*m_currBBp, thenBB); - addUntknEdge(*m_currBBp, elseBB); + CfgBlock* const thenBBp = m_cfgp->addBlock(); + CfgBlock* const elseBBp = m_cfgp->addBlock(); + CfgBlock* const contBBp = m_cfgp->addBlock(); + m_cfgp->addTakenEdge(m_currBBp, thenBBp); + m_cfgp->addUntknEdge(m_currBBp, elseBBp); // Build then branch - m_currBBp = &thenBB; + m_currBBp = thenBBp; iterateAndNextConstNull(nodep->thensp()); if (!m_cfgp) return; - if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp); // Build else branch - m_currBBp = &elseBB; + m_currBBp = elseBBp; iterateAndNextConstNull(nodep->elsesp()); if (!m_cfgp) return; - if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp); // Set continuation - m_currBBp = &contBB; + m_currBBp = contBBp; } void visit(AstWhile* nodep) override { if (!m_cfgp) return; // Create the header block - BasicBlock& headBB = addBasicBlock(); - addTakenEdge(*m_currBBp, headBB); + CfgBlock* const headBBp = m_cfgp->addBlock(); + m_cfgp->addTakenEdge(m_currBBp, headBBp); // The While goes in the header block - semantically the condition check only ... - m_currBBp = &headBB; + m_currBBp = headBBp; addStmt(nodep); // Create the body/continuation blocks - BasicBlock& bodyBB = addBasicBlock(); - BasicBlock& contBB = addBasicBlock(); - addTakenEdge(headBB, bodyBB); - addUntknEdge(headBB, contBB); + CfgBlock* const bodyBBp = m_cfgp->addBlock(); + CfgBlock* const contBBp = m_cfgp->addBlock(); + m_cfgp->addTakenEdge(headBBp, bodyBBp); + m_cfgp->addUntknEdge(headBBp, contBBp); // Build the body - m_currBBp = &bodyBB; + m_currBBp = bodyBBp; iterateAndNextConstNull(nodep->stmtsp()); iterateAndNextConstNull(nodep->incsp()); if (!m_cfgp) return; - if (m_currBBp) addTakenEdge(*m_currBBp, headBB); + if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, headBBp); // Set continuation - m_currBBp = &contBB; + m_currBBp = contBBp; } void visit(AstJumpBlock* nodep) override { if (!m_cfgp) return; - // Don't acutally need to add this 'nodep' to any block - but we could later if needed + // Don't acutally need to add this 'nodep' to any block // Create continuation block - BasicBlock& contBB = addBasicBlock(); - const bool newEntry = m_jumpBlockContp.emplace(nodep, &contBB).second; + CfgBlock* const contBBp = m_cfgp->addBlock(); + const bool newEntry = m_jumpBlockContp.emplace(nodep, contBBp).second; UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice"); // Build the body iterateAndNextConstNull(nodep->stmtsp()); if (!m_cfgp) return; - if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp); // Set continuation - m_currBBp = &contBB; + m_currBBp = contBBp; } void visit(AstJumpGo* nodep) override { if (!m_cfgp) return; @@ -190,102 +173,61 @@ class CfgBuilder final : VNVisitorConst { return; } - // Don't acutally need to add this 'nodep' to any block - but we could later if needed + // Don't acutally need to add this 'nodep' to any block // Make current block go to the continuation of the JumpBlock - addTakenEdge(*m_currBBp, *m_jumpBlockContp.at(nodep->blockp())); + m_cfgp->addTakenEdge(m_currBBp, m_jumpBlockContp.at(nodep->blockp())); // There should be no statements after a JumpGo! m_currBBp = nullptr; } // CONSTRUCTOR - explicit CfgBuilder(const AstNodeProcedure* nodep) { + explicit CfgBuilder(AstNode* stmtsp) { // Build the graph, starting from the entry block - m_currBBp = &addBasicBlock(); + m_currBBp = m_cfgp->addBlock(); m_cfgp->m_enterp = m_currBBp; // Visit each statement to build the control flow graph - iterateAndNextConstNull(nodep->stmtsp()); + iterateAndNextConstNull(stmtsp); + // If failed, stop now if (!m_cfgp) return; // The final block is the exit block m_cfgp->m_exitp = m_currBBp; - - // Remove empty blocks - except enter/exit - for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) { - if (vtxp == &m_cfgp->enter()) continue; - if (vtxp == &m_cfgp->exit()) continue; - BasicBlock* const bbp = vtxp->as(); - if (!bbp->stmtps().empty()) continue; - UASSERT(bbp->outSize1(), "Empty block should have a single successor"); - BasicBlock* const succp = const_cast(bbp->takenSuccessorp()); - for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp); - vtxp->unlinkDelete(m_cfgp.get()); - } - // Remove redundant entry block - while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) { - BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as(); - if (!succp->inSize1()) break; - m_cfgp->m_enterp->unlinkDelete(m_cfgp.get()); - m_cfgp->m_enterp = succp; - } - // Remove redundant exit block - while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) { - BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as(); - if (!prep->outSize1()) break; - m_cfgp->m_exitp->unlinkDelete(m_cfgp.get()); - m_cfgp->m_exitp = prep; - } - - // Compute reverse post-order enumeration and sort blocks, assign IDs + // Some blocks might not have predecessors if they are unreachable, remove them { - // Simple recursive algorith will do ... - std::vector postOrderEnumeration; - std::unordered_set visited; - const std::function visitBasicBlock = [&](BasicBlock* bbp) { - // Mark and skip if already visited - if (!visited.emplace(bbp).second) return; - // Visit successors - for (const V3GraphEdge& e : bbp->outEdges()) { - visitBasicBlock(static_cast(e.top())); + std::vector unreachableps; + for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) { + if (vtxp == m_cfgp->m_enterp) continue; + if (vtxp == m_cfgp->m_exitp) continue; + UASSERT_OBJ(!vtxp->outEmpty(), vtxp, "Block with no successor other than exit"); + if (vtxp->inEmpty()) unreachableps.emplace_back(vtxp); + } + while (!unreachableps.empty()) { + V3GraphVertex* const vtxp = unreachableps.back(); + unreachableps.pop_back(); + for (V3GraphEdge& edge : vtxp->outEdges()) { + --m_cfgp->m_nEdges; + if (edge.top()->inSize1()) unreachableps.emplace_back(edge.top()); } - // Add to post order enumeration - postOrderEnumeration.emplace_back(bbp); - }; - visitBasicBlock(m_cfgp->m_enterp); - const uint32_t n = postOrderEnumeration.size(); - UASSERT_OBJ(n == m_cfgp->vertices().size(), nodep, "Inconsistent enumeration size"); - - // Set size in graph - m_cfgp->m_nBasicBlocks = n; - - // Assign ids equal to the reverse post order number and sort vertices - for (uint32_t i = 0; i < postOrderEnumeration.size(); ++i) { - BasicBlock* const bbp = postOrderEnumeration[n - 1 - i]; - bbp->user(i); - m_cfgp->vertices().unlink(bbp); - m_cfgp->vertices().linkBack(bbp); + --m_cfgp->m_nBlocks; + VL_DO_DANGLING(vtxp->unlinkDelete(m_cfgp.get()), vtxp); } } - - // Assign IDs to edges - { - size_t nEdges = 0; - for (V3GraphVertex& v : m_cfgp->vertices()) { - for (V3GraphEdge& e : v.outEdges()) e.user(nEdges++); - } - // Set size in graph - m_cfgp->m_nEdges = nEdges; + // Dump the initial graph + if (dumpGraphLevel() >= 9) { + m_cfgp->rpoBlocks(); + m_cfgp->dumpDotFilePrefixed("cfg-builder-initial"); } - - if (dumpGraphLevel() >= 9) m_cfgp->dumpDotFilePrefixed("cfgbuilder"); + // Minimize it + m_cfgp->minimize(); + // Dump the final graph + if (dumpGraphLevel() >= 8) m_cfgp->dumpDotFilePrefixed("cfg-builder"); } public: - static std::unique_ptr apply(const AstNodeProcedure* nodep) { - return std::move(CfgBuilder{nodep}.m_cfgp); + static std::unique_ptr apply(AstNode* stmtsp) { + return std::move(CfgBuilder{stmtsp}.m_cfgp); } }; -std::unique_ptr V3Cfg::build(const AstNodeProcedure* nodep) { - return CfgBuilder::apply(nodep); -} +std::unique_ptr CfgGraph::build(AstNode* stmtsp) { return CfgBuilder::apply(stmtsp); } diff --git a/src/V3CfgLiveVariables.cpp b/src/V3CfgLiveVariables.cpp index b0db3aa76..f8db52ba7 100644 --- a/src/V3CfgLiveVariables.cpp +++ b/src/V3CfgLiveVariables.cpp @@ -49,9 +49,9 @@ class CfgLiveVariables final : VNVisitorConst { }; // STATE - const ControlFlowGraph& m_cfg; // The CFG beign analysed + const CfgGraph& m_cfg; // The CFG beign analysed // State for each block - BasicBlockMap m_blockState = m_cfg.makeBasicBlockMap(); + CfgBlockMap m_blockState{m_cfg.makeBlockMap()}; BlockState* m_currp = nullptr; // State of current block being analysed bool m_abort = false; // Abort analysis - unhandled construct @@ -140,7 +140,7 @@ class CfgLiveVariables final : VNVisitorConst { } // Apply transfer function of block, return true if changed - bool transfer(const BasicBlock& bb) { + bool transfer(const CfgBlock& bb) { BlockState& state = m_blockState[bb]; // liveIn = gen union (liveOut - kill) @@ -172,20 +172,21 @@ class CfgLiveVariables final : VNVisitorConst { void visit(AstWhile* nodep) override { single(nodep->condp()); } // CONSTRUCTOR - explicit CfgLiveVariables(const ControlFlowGraph& cfg) + explicit CfgLiveVariables(const CfgGraph& cfg) : m_cfg{cfg} { // For each basic block, compute the gen and kill set via visit - cfg.foreach([&](const BasicBlock& bb) { + for (const V3GraphVertex& vtx : cfg.vertices()) { + const CfgBlock& bb = static_cast(vtx); if (m_abort) return; VL_RESTORER(m_currp); m_currp = &m_blockState[bb]; for (AstNode* const stmtp : bb.stmtps()) iterateConst(stmtp); - }); + } if (m_abort) return; // Perform the flow analysis - std::deque workList; - const auto enqueue = [&](const BasicBlock& bb) { + std::deque workList; + const auto enqueue = [&](const CfgBlock& bb) { BlockState& state = m_blockState[bb]; if (state.m_isOnWorkList) return; state.m_isOnWorkList = true; @@ -195,13 +196,13 @@ class CfgLiveVariables final : VNVisitorConst { enqueue(cfg.exit()); while (!workList.empty()) { - const BasicBlock* const currp = workList.front(); + const CfgBlock* const currp = workList.front(); workList.pop_front(); BlockState& state = m_blockState[*currp]; state.m_isOnWorkList = false; // Compute meet (liveOut = union liveIn of successors) - currp->forEachSuccessor([&](const BasicBlock& bb) { + currp->forEachSuccessor([&](const CfgBlock& bb) { auto& liveIn = m_blockState[bb].m_liveIn; state.m_liveOut.insert(liveIn.begin(), liveIn.end()); }); @@ -216,7 +217,7 @@ class CfgLiveVariables final : VNVisitorConst { } public: - static std::unique_ptr> apply(const ControlFlowGraph& cfg) { + static std::unique_ptr> apply(const CfgGraph& cfg) { CfgLiveVariables analysis{cfg}; // If failed, return nullptr if (analysis.m_abort) return nullptr; @@ -231,10 +232,10 @@ public: } }; -std::unique_ptr> V3Cfg::liveVars(const ControlFlowGraph& cfg) { +std::unique_ptr> V3Cfg::liveVars(const CfgGraph& cfg) { return CfgLiveVariables::apply(cfg); } -std::unique_ptr> V3Cfg::liveVarScopes(const ControlFlowGraph& cfg) { +std::unique_ptr> V3Cfg::liveVarScopes(const CfgGraph& cfg) { return CfgLiveVariables::apply(cfg); } diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index ec18a8212..57e310df7 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -57,7 +57,7 @@ class AstToDfgVisitor final : public VNVisitor { } } - std::unique_ptr> getLiveVariables(const ControlFlowGraph& cfg) { + std::unique_ptr> getLiveVariables(const CfgGraph& cfg) { // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works if VL_CONSTEXPR_CXX17 (T_Scoped) { std::unique_ptr> result = V3Cfg::liveVarScopes(cfg); @@ -139,7 +139,7 @@ class AstToDfgVisitor final : public VNVisitor { // Gather variables live in to the given CFG. // Return nullptr if any are not supported. - std::unique_ptr> gatherLive(const ControlFlowGraph& cfg) { + std::unique_ptr> gatherLive(const CfgGraph& cfg) { // Run analysis std::unique_ptr> varps = getLiveVariables(cfg); if (!varps) { @@ -206,7 +206,7 @@ class AstToDfgVisitor final : public VNVisitor { // Potentially convertible block ++m_ctx.m_inputs; // Attempt to build CFG of AstAlways, give up if failed - std::unique_ptr cfgp = V3Cfg::build(nodep); + std::unique_ptr cfgp = CfgGraph::build(nodep->stmtsp()); if (!cfgp) { ++m_ctx.m_nonRepCfg; return false; diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index a32daedd0..a95be5b48 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -251,7 +251,13 @@ public: // Reverted VDouble0 revertNonSyn; // Reverted due to being driven from non-synthesizable vertex VDouble0 revertMultidrive; // Reverted due to multiple drivers - + // Additional stats + VDouble0 cfgTrivial; // Trivial input CFGs + VDouble0 cfgSp; // Series-paralel input CFGs + VDouble0 cfgDag; // Generic loop free input CFGs + VDouble0 cfgCyclic; // Cyclic input CFGs + VDouble0 joinUsingPathPredicate; // Num control flow joins using full path predicate + VDouble0 joinUsingBranchCondition; // Num Control flow joins using dominating branch cond } m_synt; private: @@ -314,6 +320,14 @@ private: nSyntExpect -= m_synt.synthAlways; nSyntExpect -= m_synt.synthAssign; UASSERT(nSyntNonSyn == nSyntExpect, "Inconsistent statistics / synt"); + + addStat("synt / input CFG trivial", m_synt.cfgTrivial); + addStat("synt / input CFG sp", m_synt.cfgSp); + addStat("synt / input CFG dag", m_synt.cfgDag); + addStat("synt / input CFG cyclic", m_synt.cfgCyclic); + + addStat("synt / joins using path predicate", m_synt.joinUsingPathPredicate); + addStat("synt / joins using branch condition", m_synt.joinUsingBranchCondition); } }; diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 3819542ec..230c73f70 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -508,15 +508,23 @@ class AstToDfgSynthesize final { bool operator<=(const Driver& other) const { return !(other < *this); } }; - // STATE + // STATE - Persistent DfgGraph& m_dfg; // The graph being built V3DfgSynthesisContext& m_ctx; // The context for stats AstToDfgConverter m_converter; // The convert instance to use for each construct - DfgLogic* m_logicp = nullptr; // Current logic vertex we are synthesizing - // Some debug aid: We stop after synthesizing s_dfgSynthDebugLimit vertices (if non-zero). - // This is the problematic logic (last one we synthesize), assuming a bisection search - // over s_dfgSynthDebugLimit. + // STATE - for current DfgLogic being synthesized + DfgLogic* m_logicp = nullptr; // Current logic vertex we are synthesizing + CfgBlockMap m_bbToISymTab; // Map from CfgBlock -> input symbol table + CfgBlockMap m_bbToOSymTab; // Map from CfgBlock -> output symbol table + CfgBlockMap m_bbToCondp; // Map from CfgBlock -> terminating branch condition + CfgEdgeMap m_edgeToPredicatep; // Map CfgGraphEdge -> path predicate to get there + CfgDominatorTree m_domTree; // The dominator tree of the current CFG + + // STATE - Some debug aid + // We stop after synthesizing s_dfgSynthDebugLimit vertices (if non-zero). + // This is the problematic logic (last one we synthesize), assuming a + // bisection search over s_dfgSynthDebugLimit. DfgLogic* m_debugLogicp = nullptr; // Source (upstream) cone of outputs of m_debugLogicp std::unique_ptr> m_debugOSrcConep{nullptr}; @@ -869,7 +877,7 @@ class AstToDfgSynthesize final { }); } - // Initialzie input symbol table of entry BasicBlock + // Initialzie input symbol table of entry CfgBlock void initializeEntrySymbolTable(SymTab& iSymTab) { m_logicp->forEachSource([&](DfgVertex& src) { DfgVertexVar* const vvp = src.as(); @@ -991,75 +999,152 @@ class AstToDfgSynthesize final { return joinp; } + // Merge 'thenSymTab' into 'elseSymTab' using the given predicate to join values + bool joinSymbolTables(SymTab& elseSymTab, DfgVertex* predicatep, const SymTab& thenSymTab) { + // Give up if something is not assigned on all paths ... Latch? + if (thenSymTab.size() != elseSymTab.size()) { + ++m_ctx.m_synt.nonSynLatch; + return false; + } + // Join each symbol + for (std::pair& pair : elseSymTab) { + Variable* const varp = pair.first; + // Find same variable on the else path + auto it = thenSymTab.find(varp); + // Give up if something is not assigned on all paths ... Latch? + if (it == thenSymTab.end()) { + ++m_ctx.m_synt.nonSynLatch; + return false; + } + // Join paths with the predicate + DfgVertexVar* const thenp = it->second; + DfgVertexVar* const elsep = pair.second; + DfgVertexVar* const newp = joinDrivers(varp, predicatep, thenp, elsep); + if (!newp) return false; + pair.second = newp; + } + // Done + return true; + } + + // Given two joining control flow edges, compute how to join their symbols. + // Returns the predicaete to join over, and the 'then' and 'else' blocks. + std::tuple // + howToJoin(const CfgEdge* const ap, const CfgEdge* const bp) { + // Find the closest common dominator of the two paths + const CfgBlock* const domp = m_domTree.closestCommonDominator(ap->srcp(), bp->srcp()); + // These paths join here, so 'domp' must be a branch, otherwise it's not the closest + UASSERT_OBJ(domp->isBranch(), domp, "closestCommonDominator is not a branch"); + + // The branches of the common dominator + const CfgEdge* const takenEdgep = domp->takenEdgep(); + const CfgEdge* const untknEdgep = domp->untknEdgep(); + + // We check if the taken branch dominates the path to either blocks, + // and if the untaken branch dominates the path to the other block. + // If so, we can use the branch condition as predicate, otherwise + // we must use the path predicate as there are ways to get from one + // branch of the dominator to the other. We need to be careful if + // either branches are directly to the join block. This is fine, + // it's as if there was an empty block on that critical edge which + // is dominated by that path. + + if (takenEdgep == ap || m_domTree.dominates(takenEdgep->dstp(), ap->srcp())) { + if (untknEdgep == bp || m_domTree.dominates(untknEdgep->dstp(), bp->srcp())) { + // Taken path dominates 'ap' and untaken dominates 'bp', use the branch condition + ++m_ctx.m_synt.joinUsingBranchCondition; + return std::make_tuple(m_bbToCondp[domp], ap->srcp(), bp->srcp()); + } + } else if (takenEdgep == bp || m_domTree.dominates(takenEdgep->dstp(), bp->srcp())) { + if (untknEdgep == ap || m_domTree.dominates(untknEdgep->dstp(), ap->srcp())) { + // Taken path dominates 'bp' and untaken dominates 'ap', use the branch condition + ++m_ctx.m_synt.joinUsingBranchCondition; + return std::make_tuple(m_bbToCondp[domp], bp->srcp(), ap->srcp()); + } + } + + // The branches don't dominate the joined blocks, must use the path predicate + ++m_ctx.m_synt.joinUsingPathPredicate; + + // TODO: We could do better here: use the path predicate of the closest + // cominating blocks, pick the one from the lower rank, etc, but this + // generic case is very rare, most synthesizable logic has + // series-parallel CFGs which are covered by the earlier cases. + return std::make_tuple(m_edgeToPredicatep[ap], ap->srcp(), bp->srcp()); + } + // Combine the output symbol tables of the predecessors of the given - // BasicBlock to compute the input symtol table for the given block. - bool createInputSymbolTable(SymTab& joined, const BasicBlock& bb, - const BasicBlockMap& bbToOSymTab, - const ControlFlowEdgeMap& edgeToPredicatep) { - // Input symbol table of entry block was previously initialzied - if (bb.inEmpty()) return true; + // block to compute the input symtol table for the given block. + bool createInputSymbolTable(const CfgBlock& bb) { + // The input symbol table of the given block, we are computing it now + SymTab& joined = m_bbToISymTab[bb]; - // We will fill it in here - UASSERT(joined.empty(), "Unresolved input symbol table should be empty"); - - // Fast path if there is only one predecessor - if (bb.inSize1()) { - joined = bbToOSymTab[*(bb.inEdges().frontp()->fromp()->as())]; + // Input symbol table of entry block is special + if (bb.isEnter()) { + initializeEntrySymbolTable(joined); return true; } - // Gather predecessors and the path predicates + // Current input symbol table should be empty, we will fill it in here + UASSERT(joined.empty(), "Unprocessed input symbol table should be empty"); + + // Fast path if there is only one predecessor - TODO: use less copying + if (!bb.isJoin()) { + joined = m_bbToOSymTab[bb.firstPredecessorp()]; + return true; + } + + // We also have a simpler job if there are 2 predecessors + if (bb.isTwoWayJoin()) { + DfgVertex* predicatep = nullptr; + const CfgBlock* thenp = nullptr; + const CfgBlock* elsep = nullptr; + std::tie(predicatep, thenp, elsep) + = howToJoin(bb.firstPredecessorEdgep(), bb.lastPredecessorEdgep()); + // Copy from else + joined = m_bbToOSymTab[elsep]; + // Join with then + return joinSymbolTables(joined, predicatep, m_bbToOSymTab[*thenp]); + } + + // General hard way + + // Gather predecessors struct Predecessor final { - const BasicBlock* m_bbp; - DfgVertex* m_predicatep; + const CfgBlock* m_bbp; // Predeccessor block + DfgVertex* m_predicatep; // Predicate predecessor reached this block with + const SymTab* m_oSymTabp; // Output symbol table or predecessor Predecessor() = delete; - Predecessor(const BasicBlock* bbp, DfgVertex* predicatep) + Predecessor(const CfgBlock* bbp, DfgVertex* predicatep, const SymTab* oSymTabp) : m_bbp{bbp} - , m_predicatep{predicatep} {} + , m_predicatep{predicatep} + , m_oSymTabp{oSymTabp} {} }; const std::vector predecessors = [&]() { std::vector res; for (const V3GraphEdge& edge : bb.inEdges()) { - const ControlFlowEdge& cfgEdge = static_cast(edge); - res.emplace_back(&cfgEdge.src(), edgeToPredicatep[cfgEdge]); + const CfgEdge& cfgEdge = static_cast(edge); + const CfgBlock* const predecessorp = cfgEdge.srcp(); + DfgVertex* const predicatep = m_edgeToPredicatep[cfgEdge]; + const SymTab* const oSymTabp = &m_bbToOSymTab[predecessorp]; + res.emplace_back(predecessorp, predicatep, oSymTabp); } - // Sort predecessors topologically. This way later blocks will come - // after earlier blocks, and the entry block will be first if present. + // Sort predecessors reverse topologically. This way earlier blocks + // will come after later blocks, and the entry block is last if present. std::sort(res.begin(), res.end(), [](const Predecessor& a, const Predecessor& b) { // - return a.m_bbp->id() < b.m_bbp->id(); + return *a.m_bbp > *b.m_bbp; }); return res; }(); - // Start by copying the bindings from the oldest predecessor - joined = bbToOSymTab[*predecessors[0].m_bbp]; + // Start by copying the bindings from the frist predecessor + joined = *predecessors[0].m_oSymTabp; // Join over all other predecessors for (size_t i = 1; i < predecessors.size(); ++i) { DfgVertex* const predicatep = predecessors[i].m_predicatep; - const SymTab& oSymTab = bbToOSymTab[*predecessors[i].m_bbp]; - // Give up if something is not assigned on all paths ... Latch? - if (joined.size() != oSymTab.size()) { - ++m_ctx.m_synt.nonSynLatch; - return false; - } - // Join each symbol - for (auto& pair : joined) { - Variable* const varp = pair.first; - // Find same variable on other path - auto it = oSymTab.find(varp); - // Give up if something is not assigned on all paths ... Latch? - if (it == oSymTab.end()) { - ++m_ctx.m_synt.nonSynLatch; - return false; - } - // Join paths with the block predicate - DfgVertexVar* const thenp = it->second; - DfgVertexVar* const elsep = pair.second; - DfgVertexVar* const newp = joinDrivers(varp, predicatep, thenp, elsep); - if (!newp) return false; - pair.second = newp; - } + const SymTab& oSymTab = *predecessors[i].m_oSymTabp; + if (!joinSymbolTables(joined, predicatep, oSymTab)) return false; } return true; @@ -1169,7 +1254,7 @@ class AstToDfgSynthesize final { } // Synthesize the given statements with the given input symbol table. - // Returnt true if successfolly synthesized. + // Returns true if successfolly synthesized. // Populates the given output symbol table. // Populates the given reference with the condition of the terminator branch, if any. bool synthesizeBasicBlock(SymTab& oSymTab, DfgVertex*& condpr, @@ -1259,56 +1344,53 @@ class AstToDfgSynthesize final { return true; } - // Given a basic block, and the condition of the terminating branch (if any), - // assign perdicates to the block's outgoing control flow edges. - void assignSuccessorPredicates(ControlFlowEdgeMap& edgeToPredicatep, - const BasicBlock& bb, DfgVertex* condp) { + // Assign path perdicates to the outgoing control flow edges of the given block + void assignPathPredicates(const CfgBlock& bb) { // Nothing to do for the exit block - if (bb.outEmpty()) return; + if (bb.isExit()) return; // Get the predicate of this block DfgVertex* const predp = [&]() -> DfgVertex* { // Entry block has no predecessors, use constant true - if (bb.inEmpty()) return make(m_logicp->fileline(), 1U, 1U); + if (bb.isEnter()) return make(m_logicp->fileline(), 1U, 1U); // For any other block, 'or' together all the incoming predicates const auto& inEdges = bb.inEdges(); auto it = inEdges.begin(); - DfgVertex* resp = edgeToPredicatep[static_cast(*it)]; + DfgVertex* resp = m_edgeToPredicatep[static_cast(*it)]; while (++it != inEdges.end()) { DfgOr* const orp = make(resp->fileline(), resp->dtypep()); orp->rhsp(resp); - orp->lhsp(edgeToPredicatep[static_cast(*it)]); + orp->lhsp(m_edgeToPredicatep[static_cast(*it)]); resp = orp; } return resp; }(); - if (!condp) { - // There should be 1 successors for a block with an unconditional terminator - UASSERT_OBJ(!bb.untknEdgep(), predp, "Expecting 1 successor for BasicBlock"); - // Successor predicate edge is the same - edgeToPredicatep[*bb.takenEdgep()] = predp; - } else { - // There should be 2 successors for a block with an conditional terminator - UASSERT_OBJ(bb.untknEdgep(), predp, "Expecting 2 successors for BasicBlock"); - FileLine* const flp = condp->fileline(); - AstNodeDType* const dtypep = condp->dtypep(); // Single bit - - // Predicate for taken branch: 'predp & condp' - DfgAnd* const takenPredp = make(flp, dtypep); - takenPredp->lhsp(predp); - takenPredp->rhsp(condp); - edgeToPredicatep[*bb.takenEdgep()] = takenPredp; - - // Predicate for untaken branch: 'predp & ~condp' - DfgAnd* const untknPredp = make(flp, dtypep); - untknPredp->lhsp(predp); - DfgNot* const notp = make(flp, dtypep); - notp->srcp(condp); - untknPredp->rhsp(notp); - edgeToPredicatep[*bb.untknEdgep()] = untknPredp; + // For uncondional branches, the successor predicate edge is the same + if (!bb.isBranch()) { + m_edgeToPredicatep[bb.takenEdgep()] = predp; + return; } + + // For branches, we need to factor in the branch condition + DfgVertex* const condp = m_bbToCondp[bb]; + FileLine* const flp = condp->fileline(); + AstNodeDType* const dtypep = condp->dtypep(); // Single bit + + // Predicate for taken branch: 'predp & condp' + DfgAnd* const takenPredp = make(flp, dtypep); + takenPredp->lhsp(predp); + takenPredp->rhsp(condp); + m_edgeToPredicatep[bb.takenEdgep()] = takenPredp; + + // Predicate for untaken branch: 'predp & ~condp' + DfgAnd* const untknPredp = make(flp, dtypep); + untknPredp->lhsp(predp); + DfgNot* const notp = make(flp, dtypep); + notp->srcp(condp); + untknPredp->rhsp(notp); + m_edgeToPredicatep[bb.untknEdgep()] = untknPredp; } // Add the synthesized values as drivers to the output variables of the current DfgLogic @@ -1359,7 +1441,7 @@ class AstToDfgSynthesize final { // Initialzie input symbol table initializeEntrySymbolTable(iSymTab); - // Synthesize as if it was in a single BasicBlock CFG + // Synthesize as if it was in a single CfgBlock CFG DfgVertex* condp = nullptr; const bool success = synthesizeBasicBlock(oSymTab, condp, {assignp}, iSymTab); UASSERT_OBJ(!condp, nodep, "Conditional AstAssignW ???"); @@ -1372,7 +1454,7 @@ class AstToDfgSynthesize final { } // Synthesize the given AstAlways. Returns true on success. - bool synthesizeCfg(const ControlFlowGraph& cfg) { + bool synthesizeCfg(CfgGraph& cfg) { ++m_ctx.m_synt.inputAlways; if (hasExternallyWrittenVariable(*m_logicp)) { @@ -1383,37 +1465,48 @@ class AstToDfgSynthesize final { // If there is a backward edge (loop), we can't synthesize it if (cfg.containsLoop()) { ++m_ctx.m_synt.nonSynLoop; + ++m_ctx.m_synt.cfgCyclic; return false; } - // Maps from BasicBlock to its input and output symbol tables - BasicBlockMap bbToISymTab = cfg.makeBasicBlockMap(); - BasicBlockMap bbToOSymTab = cfg.makeBasicBlockMap(); + // If it's a trivial CFG we can save on some work + if (cfg.nBlocks() == 1) { + ++m_ctx.m_synt.cfgTrivial; + } else { + // Insert two-way join blocks to aid multiplexer ordering + if (cfg.insertTwoWayJoins()) { + ++m_ctx.m_synt.cfgSp; + } else { + ++m_ctx.m_synt.cfgDag; + } + // Initialize maps needed for non-trivial CFGs + m_domTree = CfgDominatorTree{cfg}; + m_edgeToPredicatep = cfg.makeEdgeMap(); + } - // Map from ControlFlowGraphEdge to its predicate - ControlFlowEdgeMap edgeToPredicatep = cfg.makeEdgeMap(); - - // Initialzie input symbol table of entry block - initializeEntrySymbolTable(bbToISymTab[cfg.enter()]); + // Initialize CfgMaps + m_bbToISymTab = cfg.makeBlockMap(); + m_bbToOSymTab = cfg.makeBlockMap(); + m_bbToCondp = cfg.makeBlockMap(); // Synthesize all blocks - for (const V3GraphVertex& cfgVtx : cfg.vertices()) { - const BasicBlock& bb = *cfgVtx.as(); - // Symbol tables of the block - SymTab& iSymTab = bbToISymTab[bb]; - SymTab& oSymTab = bbToOSymTab[bb]; - // Join symbol tables from predecessor blocks - if (!createInputSymbolTable(iSymTab, bb, bbToOSymTab, edgeToPredicatep)) return false; - // Condition of the terminating branch, if any - DfgVertex* condp = nullptr; - // Synthesize the block - if (!synthesizeBasicBlock(oSymTab, condp, bb.stmtps(), iSymTab)) return false; - // Set the predicates on the successor edges - assignSuccessorPredicates(edgeToPredicatep, bb, condp); + for (const V3GraphVertex& vtx : cfg.vertices()) { + const CfgBlock& bb = static_cast(vtx); + // Prepare the input symbol table of this block (enter, or join predecessor blocks) + if (!createInputSymbolTable(bb)) return false; + // Synthesize this block + if (!synthesizeBasicBlock(m_bbToOSymTab[bb], // + m_bbToCondp[bb], // + bb.stmtps(), // + m_bbToISymTab[bb])) { + return false; + } + // Set the path predicates on the successor edges + assignPathPredicates(bb); } // Add resolved output variable drivers - return addSynthesizedOutput(bbToOSymTab[cfg.exit()]); + return addSynthesizedOutput(m_bbToOSymTab[cfg.exit()]); } // Synthesize a DfgLogic into regular vertices. Returns ture on success. @@ -1676,7 +1769,7 @@ void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) { if (VN_IS(logicp->nodep(), AssignW)) return true; // Synthesize always blocks with no more than 4 basic blocks and 4 edges // These are usually simple branches (if (rst) ... else ...), or close to it - return logicp->cfg().nBasicBlocks() <= 4 && logicp->cfg().nEdges() <= 4; + return logicp->cfg().nBlocks() <= 4 && logicp->cfg().nEdges() <= 4; }); if (doIt) varps.emplace_back(&var); } diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index 9e45add00..316f2c3ae 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -346,7 +346,7 @@ public: class DfgLogic final : public DfgVertexVariadic { // Generic vertex representing a whole combinational process AstNode* const m_nodep; // The Ast logic represented by this vertex - const std::unique_ptr m_cfgp; + const std::unique_ptr m_cfgp; // Vertices this logic was synthesized into. Excluding variables std::vector m_synth; @@ -356,7 +356,7 @@ public: , m_nodep{nodep} , m_cfgp{nullptr} {} - DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr cfgp) + DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr cfgp) : DfgVertexVariadic{dfg, dfgType(), nodep->fileline(), nullptr, 1u} , m_nodep{nodep} , m_cfgp{std::move(cfgp)} {} @@ -366,7 +366,8 @@ public: void addInput(DfgVertexVar* varp) { addSource()->relinkSource(varp); } AstNode* nodep() const { return m_nodep; } - const ControlFlowGraph& cfg() const { return *m_cfgp; } + CfgGraph& cfg() { return *m_cfgp; } + const CfgGraph& cfg() const { return *m_cfgp; } std::vector& synth() { return m_synth; } const std::vector& synth() const { return m_synth; } diff --git a/src/V3List.h b/src/V3List.h index 89d6d36b3..0383b420d 100644 --- a/src/V3List.h +++ b/src/V3List.h @@ -241,6 +241,7 @@ public: bool empty() const { return !m_headp; } bool hasSingleElement() const { return m_headp && m_headp == m_lastp; } bool hasMultipleElements() const { return m_headp && m_headp != m_lastp; } + bool hasTwoElements() const { return m_headp && toLinks(m_headp).m_nextp == m_lastp; } // These return pointers, as we often want to unlink/delete them, and can also signal empty T_Element* frontp() { return static_cast(m_headp); } diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 48fe8d28f..03eab0623 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -44,9 +44,10 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ open(pdeclFile, 'w', encoding="utf8") as pdeclFh, \ open(checkFile, 'w', encoding="utf8") as checkFh: for line in rdFh: - if "// UNOPTFLAT" in line: + line, _, cmt = line.partition("//") + cmt, _, _ = cmt.partition("//") + if "UNOPTFLAT" in cmt: nExpectedCycles += 1 - line = line.split("//")[0] m = re.search(r'`signal\((\w+),', line) if not m: continue diff --git a/test_regress/t/t_dfg_synthesis.py b/test_regress/t/t_dfg_synthesis.py index bd229b216..6d767dc14 100755 --- a/test_regress/t/t_dfg_synthesis.py +++ b/test_regress/t/t_dfg_synthesis.py @@ -70,13 +70,14 @@ test.compile(verilator_flags2=[ "--stats", "--build", "--fdfg-synthesize-all", + "-fno-dfg-pre-inline", "-fno-dfg-post-inline", - "-fno-dfg-scoped", "--exe", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", "-fno-const-before-dfg", # Otherwise V3Const makes testing painful + "-fno-split", # Dfg will take care of it "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", @@ -84,14 +85,13 @@ test.compile(verilator_flags2=[ ]) # yapf:disable test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", - r'DFG pre inline Synthesis, synt / always blocks considered\s+(\d+)$', + r'DFG scoped Synthesis, synt / always blocks considered\s+(\d+)$', nAlwaysSynthesized + nAlwaysReverted + nAlwaysNotSynthesized) test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", - r'DFG pre inline Synthesis, synt / always blocks synthesized\s+(\d+)$', + r'DFG scoped Synthesis, synt / always blocks synthesized\s+(\d+)$', nAlwaysSynthesized + nAlwaysReverted) test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", - r'DFG pre inline Synthesis, synt / reverted \(multidrive\)\s+(\d)$', - nAlwaysReverted) + r'DFG scoped Synthesis, synt / reverted \(multidrive\)\s+(\d)$', nAlwaysReverted) # Execute test to check equivalence test.execute(executable=test.obj_dir + "/obj_opt/Vopt") diff --git a/test_regress/t/t_dfg_synthesis.v b/test_regress/t/t_dfg_synthesis.v index 46f3e298b..ad76285cf 100644 --- a/test_regress/t/t_dfg_synthesis.v +++ b/test_regress/t/t_dfg_synthesis.v @@ -154,6 +154,176 @@ module t ( end `signal(CONDITONAL_F, condigional_f); + logic [2:0] conditional_g; + always_comb begin + if (rand_a[0]) begin + if (rand_a[1]) begin + if (rand_a[2]) begin + conditional_g = 3'b111; + end else begin + conditional_g = 3'b011; + end + end else begin + if (rand_a[2]) begin + conditional_g = 3'b101; + end else begin + conditional_g = 3'b001; + end + end + end else begin + if (rand_a[1]) begin + if (rand_a[2]) begin + conditional_g = 3'b110; + end else begin + conditional_g = 3'b010; + end + end else begin + if (rand_a[2]) begin + conditional_g = 3'b100; + end else begin + conditional_g = 3'b000; + end + end + end + end + `signal(CONDITONAL_G, conditional_g); + + logic [2:0] conditional_h; + always_comb begin + if (rand_a[0]) begin + if (rand_a[1]) begin + conditional_h = 3'b011; + if (rand_a[2]) begin + conditional_h = 3'b111; + end + end else begin + conditional_h = 3'b001; + if (rand_a[2]) begin + conditional_h = 3'b101; + end + end + end else begin + if (rand_a[1]) begin + conditional_h = 3'b010; + if (rand_a[2]) begin + conditional_h = 3'b110; + end + end else begin + conditional_h = 3'b000; + if (rand_a[2]) begin + conditional_h = 3'b100; + end + end + end + end + `signal(CONDITONAL_H, conditional_h); + + logic [2:0] conditional_i; + always_comb begin // Dumbass trailing zeroes count + do begin + conditional_i = 3'd0; + if (rand_a[0]) break; + conditional_i = 3'd1; + if (rand_a[1]) break; + conditional_i = 3'd2; + if (rand_a[2]) break; + conditional_i = 3'd3; + if (rand_a[3]) break; + conditional_i = 3'd4; + end while (0); + end + `signal(CONDITONAL_I, conditional_i); + + logic [2:0] conditional_j; + always_comb begin // Even more dumbass trailing ones count + do begin + conditional_j = 3'd0; + if (rand_a[0]) begin + conditional_j = 3'd1; + end else begin + break; + end + if (rand_a[1]) begin + conditional_j = 3'd2; + end else begin + break; + end + if (rand_a[2]) begin + conditional_j = 3'd3; + end else begin + break; + end + if (rand_a[3]) begin + conditional_j = 3'd4; + end + end while (0); + end + `signal(CONDITONAL_J, conditional_j); + + logic [2:0] conditional_k; + always_comb begin + if (rand_b[0]) begin + do begin + conditional_k = 3'd0; + if (rand_a[0]) break; + conditional_k = 3'd1; + if (rand_a[1]) break; + conditional_k = 3'd2; + if (rand_a[2]) break; + conditional_k = 3'd3; + if (rand_a[3]) break; + conditional_k = 3'd4; + end while (0); + end else begin + do begin + conditional_k = 3'd0; + if (rand_a[0]) begin + conditional_k = 3'd1; + end else begin + break; + end + if (rand_a[1]) begin + conditional_k = 3'd2; + end else begin + break; + end + if (rand_a[2]) begin + conditional_k = 3'd3; + end else begin + break; + end + if (rand_a[3]) begin + conditional_k = 3'd4; + end + end while (0); + end + end + `signal(CONDITONAL_K, conditional_k); + + logic [1:0] conditional_l_a; + logic [1:0] conditional_l_b; + always_comb begin + do begin + conditional_l_a = 2'd0; + if (rand_a[1:0] == 2'd0) break; + conditional_l_a = 2'd1; + if (rand_a[1:0] == 2'd1) break; + conditional_l_a = 2'd2; + if (rand_a[1:0] == 2'd2) break; + conditional_l_a = 2'd3; + end while (0); + do begin + conditional_l_b = 2'd0; + if (rand_b[1:0] == 2'd0) break; + conditional_l_b = 2'd1; + if (rand_b[1:0] == 2'd1) break; + conditional_l_b = 2'd2; + if (rand_b[1:0] == 2'd2) break; + conditional_l_b = 2'd3; + end while (0); + end + `signal(CONDITONAL_L, {conditional_l_b, conditional_l_a}); + logic [7:0] partial_conditional_a; always_comb begin partial_conditional_a[1:0] = 2'd0; @@ -225,4 +395,111 @@ module t ( partial_temporary_a = partial_temporary_tmp; end `signal(PARTIAL_TEMPORARY, partial_temporary_a); + + logic circular_0_x; + logic circular_0_y; + logic circular_0_z; + always_comb begin + circular_0_x = 1'b0; + circular_0_y = 1'd0; + if (rand_b[0]) begin + circular_0_x = 1'b1; + if (circular_0_z) begin + circular_0_y = 1'd1; + end + end + end + assign circular_0_z = circular_0_x & rand_a[0]; + `signal(CIRCULAR_0, {circular_0_x, circular_0_y, circular_0_z}); + + logic [2:0] nsp_a; // Non series-parallel CFG + always_comb begin + do begin + nsp_a = 3'd0; // BB 0 -> BB 1 / BB 2 + if (rand_a[1:0] == 2'd0) begin + nsp_a = 3'd1; // BB 1 -> BB 4 + end else begin + nsp_a = 3'd2; // BB 2 -> BB 3 / BB 4 + if (rand_a[1:0] == 2'd1) begin + nsp_a = 3'd3; // BB3 -> BB 5 + break; + end + end + nsp_a = 3'd4; // BB 4 -> BB 5 + end while (0); + //nsp_a = 3'd5; // BB 5 + end + `signal(NSP_A, nsp_a); + + logic [2:0] nsp_b; // Non series-parallel CFG + always_comb begin + do begin + nsp_b = 3'd0; + if (rand_a[1:0] == 2'd0) begin + nsp_b = 3'd1; + end else begin + nsp_b = 3'd2; + if (rand_a[1:0] == 2'd1) begin + nsp_b = 3'd3; + break; + end else begin + nsp_b = 3'd4; + if (rand_a[1:0] == 2'd2) begin + nsp_b = 3'd5; + end else begin + nsp_b = 3'd6; + break; + end + end + end + nsp_b = 3'd7; + end while (0); + end + `signal(NSP_B, nsp_b); + + logic [2:0] part_sp_a; // Contains series-parallel sub-graph CFG + always_comb begin + do begin + part_sp_a = 3'd0; + if (rand_a[0]) begin + part_sp_a = 3'd1; + if (rand_a[1]) begin + part_sp_a = 3'd2; + end + end else begin + part_sp_a = 3'd3; + if (rand_a[2]) begin + part_sp_a = 3'd4; + if (rand_a[3]) begin + part_sp_a = 3'd5; + end + break; + end + end + part_sp_a = 3'd6; + if (rand_a[4]) begin + part_sp_a = 3'd7; + end + end while (0); + end + `signal(PART_SP_A, part_sp_a); + + logic [1:0] both_break; + always_comb begin + do begin + if (rand_a[0]) begin + both_break = 2'd0; + break; + end else begin + both_break = 2'd1; + break; + end + // Unreachable + if (rand_a[1]) begin + both_break = 2'd2; + end + end while(0); + end + `signal(BOTH_BREAK, both_break); + endmodule