Optimize multiplexers in Dfg synthesis (#6331)
The previous algorithm was designed to handle the general case where a full control flow path predicate is required to select which value to use when synthesizing control flow join point in an always block. Here we add a better algorithm that tries to use the predicate of the closest dominating branch if the branch paths dominate the joining paths. This is almost universally true in synthesizable logic (RTLMeter has no exceptions), however there are cases where this is not applicable, for which we fall back on the previous generic algorithm. Overall this significantly simplifies the synthesized Dfg graphs and enables further optimization.
This commit is contained in:
parent
c2cac8a7fd
commit
02e64f0795
287
src/V3Cfg.cpp
287
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<const BasicBlock&>(vtx);
|
||||
for (const V3GraphEdge& edge : current.outEdges()) {
|
||||
const BasicBlock& successor = *static_cast<const BasicBlock*>(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<BasicBlock>();
|
||||
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<CfgBlock*>& 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<CfgBlock*> 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<CfgEdge&>(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<const CfgBlock&>(vtx);
|
||||
for (const V3GraphEdge& edge : current.outEdges()) {
|
||||
const CfgBlock& successor = *static_cast<const CfgBlock*>(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<CfgBlock*>(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<CfgBlock*>(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<AstNodeStmt*> 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<CfgEdge*> criticalEdges;
|
||||
criticalEdges.reserve(m_nEdges);
|
||||
for (V3GraphVertex& vtx : vertices()) {
|
||||
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
|
||||
if (!bb.isBranch()) continue;
|
||||
for (V3GraphEdge& edge : vtx.outEdges()) {
|
||||
const CfgBlock& succ = static_cast<const CfgBlock&>(*edge.top());
|
||||
if (!succ.isJoin()) continue;
|
||||
criticalEdges.emplace_back(static_cast<CfgEdge*>(&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<CfgBlock&>(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<const CfgBlock*>()} {
|
||||
|
||||
// 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<const CfgBlock&>(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<const CfgBlock&>(*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;
|
||||
}
|
||||
|
|
|
|||
539
src/V3Cfg.h
539
src/V3Cfg.h
|
|
@ -68,69 +68,103 @@
|
|||
#include <vector>
|
||||
|
||||
//######################################################################
|
||||
// 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 <typename T_Key, size_t (T_Key::*Index)() const, typename T_Value>
|
||||
class DenseMap final {
|
||||
std::vector<T_Value> 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 <typename T_Key, typename T_Value>
|
||||
class CfgMap;
|
||||
template <typename T_Value>
|
||||
class CfgBlockMap;
|
||||
template <typename T_Value>
|
||||
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 <typename T_Key, typename T_Value>
|
||||
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<AstNodeStmt*> 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<AstNodeStmt*> 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<AstNodeStmt*>& 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<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : outEdges()) f(*edge.top()->as<BasicBlock>());
|
||||
}
|
||||
|
||||
void forEachPredecessor(std::function<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : inEdges()) f(*edge.fromp()->as<BasicBlock>());
|
||||
// Iterators
|
||||
void forEachSuccessor(std::function<void(const CfgBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : outEdges()) f(*static_cast<CfgBlock*>(edge.top()));
|
||||
}
|
||||
void forEachPredecessor(std::function<void(const CfgBlock&)> f) const {
|
||||
for (const V3GraphEdge& edge : inEdges()) f(*static_cast<CfgBlock*>(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 <typename T_Key, typename T_Value>
|
||||
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<BasicBlock*>(fromp()); }
|
||||
const BasicBlock& dst() const { return *static_cast<BasicBlock*>(top()); }
|
||||
// Source/destination CfgBlock
|
||||
const CfgBlock* srcp() const { return static_cast<const CfgBlock*>(fromp()); }
|
||||
const CfgBlock* dstp() const { return static_cast<const CfgBlock*>(top()); }
|
||||
CfgBlock* srcp() { return static_cast<CfgBlock*>(fromp()); }
|
||||
CfgBlock* dstp() { return static_cast<CfgBlock*>(top()); }
|
||||
|
||||
// For Graphviz dumps only
|
||||
std::string dotLabel() const override;
|
||||
};
|
||||
|
||||
template <typename T_Value>
|
||||
using BasicBlockMap = DenseMap<BasicBlock, &BasicBlock::id, T_Value>;
|
||||
//######################################################################
|
||||
// CfgGraph - The control flow graph
|
||||
|
||||
template <typename T_Value>
|
||||
using ControlFlowEdgeMap = DenseMap<ControlFlowEdge, &ControlFlowEdge::id, T_Value>;
|
||||
|
||||
// The control flow graph
|
||||
class ControlFlowGraph final : public V3Graph {
|
||||
class CfgGraph final : public V3Graph {
|
||||
friend class CfgBlock;
|
||||
friend class CfgEdge;
|
||||
template <typename T_Key, typename T_Value>
|
||||
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<void(const BasicBlock&)> f) const {
|
||||
for (const V3GraphVertex& vtx : vertices()) f(*vtx.as<BasicBlock>());
|
||||
// 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<CfgGraph> 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 <typename T_Value>
|
||||
BasicBlockMap<T_Value> makeBasicBlockMap() const {
|
||||
return BasicBlockMap<T_Value>{nBasicBlocks()};
|
||||
}
|
||||
// Create a ControlFlowEdgeMap map for this graph
|
||||
inline CfgBlockMap<T_Value> makeBlockMap() const;
|
||||
// Create a CfgEdgeMap map for this graph
|
||||
template <typename T_Value>
|
||||
ControlFlowEdgeMap<T_Value> makeEdgeMap() const {
|
||||
return ControlFlowEdgeMap<T_Value>{nEdges()};
|
||||
}
|
||||
inline CfgEdgeMap<T_Value> 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 <typename T_Key, typename T_Value>
|
||||
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<const ControlFlowEdge*>(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<T_Value> m_map; // The map, stored as a vector
|
||||
|
||||
protected:
|
||||
// CONSTRUCTOR
|
||||
explicit CfgMap(const CfgGraph* cfgp, size_t size)
|
||||
: m_cfgp{cfgp}
|
||||
, m_created{cfgp->m_nEdits}
|
||||
, m_map{size} {}
|
||||
|
||||
public:
|
||||
// Can create an empty map
|
||||
CfgMap()
|
||||
: m_cfgp{nullptr}
|
||||
, m_created{0} {}
|
||||
|
||||
// Copyable, movable
|
||||
CfgMap(const CfgMap<T_Key, T_Value>&) = default;
|
||||
CfgMap(CfgMap<T_Key, T_Value>&&) = default;
|
||||
CfgMap<T_Key, T_Value>& operator=(const CfgMap<T_Key, T_Value>&) = default;
|
||||
CfgMap<T_Key, T_Value>& operator=(CfgMap<T_Key, T_Value>&&) = default;
|
||||
|
||||
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 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<const ControlFlowEdge*>(backp) : nullptr;
|
||||
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());
|
||||
}
|
||||
|
||||
const BasicBlock* BasicBlock::takenSuccessorp() const {
|
||||
const ControlFlowEdge* const edgep = takenEdgep();
|
||||
return edgep ? static_cast<const BasicBlock*>(edgep->top()) : nullptr;
|
||||
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 BasicBlock* BasicBlock::untknSuccessorp() const {
|
||||
const ControlFlowEdge* const edgep = untknEdgep();
|
||||
return edgep ? static_cast<const BasicBlock*>(edgep->top()) : nullptr;
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
ControlFlowEdge::ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp)
|
||||
: V3GraphEdge{graphp, srcp, dstp, 1, false} {}
|
||||
template <typename T_Value>
|
||||
class CfgBlockMap final : public CfgMap<CfgBlock, T_Value> {
|
||||
friend class CfgGraph;
|
||||
// CONSTRUCTOR - Create one via CfgGraph::makeBlockMap
|
||||
explicit CfgBlockMap(const CfgGraph* cfgp)
|
||||
: CfgMap<CfgBlock, T_Value>{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 <typename T_Value>
|
||||
class CfgEdgeMap final : public CfgMap<CfgEdge, T_Value> {
|
||||
friend class CfgGraph;
|
||||
// CONSTRUCTOR - Create one via CfgGraph::makeEdgeMap
|
||||
explicit CfgEdgeMap(const CfgGraph* cfgp)
|
||||
: CfgMap<CfgEdge, T_Value>{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<CfgEdge*>(outEdges().frontp());
|
||||
}
|
||||
const CfgEdge* CfgBlock::takenEdgep() const { // It's always the first edge
|
||||
return isExit() ? nullptr : static_cast<const CfgEdge*>(outEdges().frontp());
|
||||
}
|
||||
CfgEdge* CfgBlock::untknEdgep() { // It's always the second (last) edge
|
||||
return isBranch() ? static_cast<CfgEdge*>(outEdges().backp()) : nullptr;
|
||||
}
|
||||
const CfgEdge* CfgBlock::untknEdgep() const { // It's always the second (last) edge
|
||||
return isBranch() ? static_cast<const CfgEdge*>(outEdges().backp()) : nullptr;
|
||||
}
|
||||
CfgBlock* CfgBlock::takenp() {
|
||||
return isExit() ? nullptr : static_cast<CfgBlock*>(outEdges().frontp()->top());
|
||||
}
|
||||
const CfgBlock* CfgBlock::takenp() const {
|
||||
return isExit() ? nullptr : static_cast<CfgBlock*>(outEdges().frontp()->top());
|
||||
}
|
||||
CfgBlock* CfgBlock::untknp() {
|
||||
return isBranch() ? static_cast<CfgBlock*>(outEdges().backp()->top()) : nullptr;
|
||||
}
|
||||
const CfgBlock* CfgBlock::untknp() const {
|
||||
return isBranch() ? static_cast<const CfgBlock*>(outEdges().backp()->top()) : nullptr;
|
||||
}
|
||||
|
||||
// Predecessor edges
|
||||
CfgEdge* CfgBlock::firstPredecessorEdgep() { //
|
||||
return static_cast<CfgEdge*>(inEdges().frontp());
|
||||
}
|
||||
const CfgEdge* CfgBlock::firstPredecessorEdgep() const { //
|
||||
return static_cast<const CfgEdge*>(inEdges().frontp());
|
||||
}
|
||||
CfgEdge* CfgBlock::lastPredecessorEdgep() { //
|
||||
return static_cast<CfgEdge*>(inEdges().backp());
|
||||
}
|
||||
const CfgEdge* CfgBlock::lastPredecessorEdgep() const { //
|
||||
return static_cast<const CfgEdge*>(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 <typename T_Value>
|
||||
CfgBlockMap<T_Value> CfgGraph::makeBlockMap() const {
|
||||
return CfgBlockMap<T_Value>{this};
|
||||
}
|
||||
template <typename T_Value>
|
||||
CfgEdgeMap<T_Value> CfgGraph::makeEdgeMap() const {
|
||||
return CfgEdgeMap<T_Value>{this};
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// CfgDominatorTree
|
||||
|
||||
class CfgDominatorTree final {
|
||||
// STATE
|
||||
CfgBlockMap<const CfgBlock*> 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<const ControlFlowGraph> 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<std::vector<AstVar*>> liveVars(const ControlFlowGraph&);
|
||||
std::unique_ptr<std::vector<AstVar*>> liveVars(const CfgGraph&);
|
||||
|
||||
// Same as liveVars, but return AstVarScopes insted
|
||||
std::unique_ptr<std::vector<AstVarScope*>> liveVarScopes(const ControlFlowGraph&);
|
||||
std::unique_ptr<std::vector<AstVarScope*>> liveVarScopes(const CfgGraph&);
|
||||
|
||||
} //namespace V3Cfg
|
||||
|
||||
#endif // VERILATOR_V3CFG_H_
|
||||
|
|
|
|||
|
|
@ -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<ControlFlowGraph> m_cfgp{new ControlFlowGraph};
|
||||
std::unique_ptr<CfgGraph> 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<AstJumpBlock*, BasicBlock*> m_jumpBlockContp;
|
||||
std::unordered_map<AstJumpBlock*, CfgBlock*> 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
|
||||
// Some blocks might not have predecessors if they are unreachable, remove them
|
||||
{
|
||||
std::vector<V3GraphVertex*> unreachableps;
|
||||
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<BasicBlock>();
|
||||
if (!bbp->stmtps().empty()) continue;
|
||||
UASSERT(bbp->outSize1(), "Empty block should have a single successor");
|
||||
BasicBlock* const succp = const_cast<BasicBlock*>(bbp->takenSuccessorp());
|
||||
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
|
||||
vtxp->unlinkDelete(m_cfgp.get());
|
||||
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);
|
||||
}
|
||||
// Remove redundant entry block
|
||||
while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) {
|
||||
BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as<BasicBlock>();
|
||||
if (!succp->inSize1()) break;
|
||||
m_cfgp->m_enterp->unlinkDelete(m_cfgp.get());
|
||||
m_cfgp->m_enterp = succp;
|
||||
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());
|
||||
}
|
||||
// Remove redundant exit block
|
||||
while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) {
|
||||
BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as<BasicBlock>();
|
||||
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
|
||||
{
|
||||
// Simple recursive algorith will do ...
|
||||
std::vector<BasicBlock*> postOrderEnumeration;
|
||||
std::unordered_set<BasicBlock*> visited;
|
||||
const std::function<void(BasicBlock*)> 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<BasicBlock*>(e.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++);
|
||||
// Dump the initial graph
|
||||
if (dumpGraphLevel() >= 9) {
|
||||
m_cfgp->rpoBlocks();
|
||||
m_cfgp->dumpDotFilePrefixed("cfg-builder-initial");
|
||||
}
|
||||
// Set size in graph
|
||||
m_cfgp->m_nEdges = nEdges;
|
||||
}
|
||||
|
||||
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<ControlFlowGraph> apply(const AstNodeProcedure* nodep) {
|
||||
return std::move(CfgBuilder{nodep}.m_cfgp);
|
||||
static std::unique_ptr<CfgGraph> apply(AstNode* stmtsp) {
|
||||
return std::move(CfgBuilder{stmtsp}.m_cfgp);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<const ControlFlowGraph> V3Cfg::build(const AstNodeProcedure* nodep) {
|
||||
return CfgBuilder::apply(nodep);
|
||||
}
|
||||
std::unique_ptr<CfgGraph> CfgGraph::build(AstNode* stmtsp) { return CfgBuilder::apply(stmtsp); }
|
||||
|
|
|
|||
|
|
@ -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<BlockState> m_blockState = m_cfg.makeBasicBlockMap<BlockState>();
|
||||
CfgBlockMap<BlockState> m_blockState{m_cfg.makeBlockMap<BlockState>()};
|
||||
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<const CfgBlock&>(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<const BasicBlock*> workList;
|
||||
const auto enqueue = [&](const BasicBlock& bb) {
|
||||
std::deque<const CfgBlock*> 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<std::vector<Variable*>> apply(const ControlFlowGraph& cfg) {
|
||||
static std::unique_ptr<std::vector<Variable*>> 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<std::vector<AstVar*>> V3Cfg::liveVars(const ControlFlowGraph& cfg) {
|
||||
std::unique_ptr<std::vector<AstVar*>> V3Cfg::liveVars(const CfgGraph& cfg) {
|
||||
return CfgLiveVariables</* T_Scoped: */ false>::apply(cfg);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<AstVarScope*>> V3Cfg::liveVarScopes(const ControlFlowGraph& cfg) {
|
||||
std::unique_ptr<std::vector<AstVarScope*>> V3Cfg::liveVarScopes(const CfgGraph& cfg) {
|
||||
return CfgLiveVariables</* T_Scoped: */ true>::apply(cfg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class AstToDfgVisitor final : public VNVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Variable*>> getLiveVariables(const ControlFlowGraph& cfg) {
|
||||
std::unique_ptr<std::vector<Variable*>> 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<std::vector<AstVarScope*>> 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<std::vector<DfgVertexVar*>> gatherLive(const ControlFlowGraph& cfg) {
|
||||
std::unique_ptr<std::vector<DfgVertexVar*>> gatherLive(const CfgGraph& cfg) {
|
||||
// Run analysis
|
||||
std::unique_ptr<std::vector<Variable*>> 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<const ControlFlowGraph> cfgp = V3Cfg::build(nodep);
|
||||
std::unique_ptr<CfgGraph> cfgp = CfgGraph::build(nodep->stmtsp());
|
||||
if (!cfgp) {
|
||||
++m_ctx.m_nonRepCfg;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T_Scoped> 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<SymTab> m_bbToISymTab; // Map from CfgBlock -> input symbol table
|
||||
CfgBlockMap<SymTab> m_bbToOSymTab; // Map from CfgBlock -> output symbol table
|
||||
CfgBlockMap<DfgVertex*> m_bbToCondp; // Map from CfgBlock -> terminating branch condition
|
||||
CfgEdgeMap<DfgVertex*> 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<std::unordered_set<const DfgVertex*>> 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<DfgVertexVar>();
|
||||
|
|
@ -991,75 +999,152 @@ class AstToDfgSynthesize final {
|
|||
return joinp;
|
||||
}
|
||||
|
||||
// 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<SymTab>& bbToOSymTab,
|
||||
const ControlFlowEdgeMap<DfgVertex*>& edgeToPredicatep) {
|
||||
// Input symbol table of entry block was previously initialzied
|
||||
if (bb.inEmpty()) return true;
|
||||
|
||||
// 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<BasicBlock>())];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gather predecessors and the path predicates
|
||||
struct Predecessor final {
|
||||
const BasicBlock* m_bbp;
|
||||
DfgVertex* m_predicatep;
|
||||
Predecessor() = delete;
|
||||
Predecessor(const BasicBlock* bbp, DfgVertex* predicatep)
|
||||
: m_bbp{bbp}
|
||||
, m_predicatep{predicatep} {}
|
||||
};
|
||||
|
||||
const std::vector<Predecessor> predecessors = [&]() {
|
||||
std::vector<Predecessor> res;
|
||||
for (const V3GraphEdge& edge : bb.inEdges()) {
|
||||
const ControlFlowEdge& cfgEdge = static_cast<const ControlFlowEdge&>(edge);
|
||||
res.emplace_back(&cfgEdge.src(), edgeToPredicatep[cfgEdge]);
|
||||
}
|
||||
// Sort predecessors topologically. This way later blocks will come
|
||||
// after earlier blocks, and the entry block will be first if present.
|
||||
std::sort(res.begin(), res.end(), [](const Predecessor& a, const Predecessor& b) { //
|
||||
return a.m_bbp->id() < b.m_bbp->id();
|
||||
});
|
||||
return res;
|
||||
}();
|
||||
|
||||
// Start by copying the bindings from the oldest predecessor
|
||||
joined = bbToOSymTab[*predecessors[0].m_bbp];
|
||||
// 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];
|
||||
// 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 (joined.size() != oSymTab.size()) {
|
||||
if (thenSymTab.size() != elseSymTab.size()) {
|
||||
++m_ctx.m_synt.nonSynLatch;
|
||||
return false;
|
||||
}
|
||||
// Join each symbol
|
||||
for (auto& pair : joined) {
|
||||
for (std::pair<Variable* const, DfgVertexVar*>& pair : elseSymTab) {
|
||||
Variable* const varp = pair.first;
|
||||
// Find same variable on other path
|
||||
auto it = oSymTab.find(varp);
|
||||
// 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 == oSymTab.end()) {
|
||||
if (it == thenSymTab.end()) {
|
||||
++m_ctx.m_synt.nonSynLatch;
|
||||
return false;
|
||||
}
|
||||
// Join paths with the block predicate
|
||||
// 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<DfgVertex*, const CfgBlock*, const CfgBlock*> //
|
||||
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
|
||||
// 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];
|
||||
|
||||
// Input symbol table of entry block is special
|
||||
if (bb.isEnter()) {
|
||||
initializeEntrySymbolTable(joined);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 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 CfgBlock* bbp, DfgVertex* predicatep, const SymTab* oSymTabp)
|
||||
: m_bbp{bbp}
|
||||
, m_predicatep{predicatep}
|
||||
, m_oSymTabp{oSymTabp} {}
|
||||
};
|
||||
|
||||
const std::vector<Predecessor> predecessors = [&]() {
|
||||
std::vector<Predecessor> res;
|
||||
for (const V3GraphEdge& edge : bb.inEdges()) {
|
||||
const CfgEdge& cfgEdge = static_cast<const CfgEdge&>(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 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 > *b.m_bbp;
|
||||
});
|
||||
return res;
|
||||
}();
|
||||
|
||||
// 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 = *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,39 +1344,37 @@ 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<DfgVertex*>& 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<DfgConst>(m_logicp->fileline(), 1U, 1U);
|
||||
if (bb.isEnter()) return make<DfgConst>(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<const ControlFlowEdge&>(*it)];
|
||||
DfgVertex* resp = m_edgeToPredicatep[static_cast<const CfgEdge&>(*it)];
|
||||
while (++it != inEdges.end()) {
|
||||
DfgOr* const orp = make<DfgOr>(resp->fileline(), resp->dtypep());
|
||||
orp->rhsp(resp);
|
||||
orp->lhsp(edgeToPredicatep[static_cast<const ControlFlowEdge&>(*it)]);
|
||||
orp->lhsp(m_edgeToPredicatep[static_cast<const CfgEdge&>(*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");
|
||||
// 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
|
||||
|
||||
|
|
@ -1299,7 +1382,7 @@ class AstToDfgSynthesize final {
|
|||
DfgAnd* const takenPredp = make<DfgAnd>(flp, dtypep);
|
||||
takenPredp->lhsp(predp);
|
||||
takenPredp->rhsp(condp);
|
||||
edgeToPredicatep[*bb.takenEdgep()] = takenPredp;
|
||||
m_edgeToPredicatep[bb.takenEdgep()] = takenPredp;
|
||||
|
||||
// Predicate for untaken branch: 'predp & ~condp'
|
||||
DfgAnd* const untknPredp = make<DfgAnd>(flp, dtypep);
|
||||
|
|
@ -1307,8 +1390,7 @@ class AstToDfgSynthesize final {
|
|||
DfgNot* const notp = make<DfgNot>(flp, dtypep);
|
||||
notp->srcp(condp);
|
||||
untknPredp->rhsp(notp);
|
||||
edgeToPredicatep[*bb.untknEdgep()] = untknPredp;
|
||||
}
|
||||
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<SymTab> bbToISymTab = cfg.makeBasicBlockMap<SymTab>();
|
||||
BasicBlockMap<SymTab> bbToOSymTab = cfg.makeBasicBlockMap<SymTab>();
|
||||
// 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<DfgVertex*>();
|
||||
}
|
||||
|
||||
// Map from ControlFlowGraphEdge to its predicate
|
||||
ControlFlowEdgeMap<DfgVertex*> edgeToPredicatep = cfg.makeEdgeMap<DfgVertex*>();
|
||||
|
||||
// Initialzie input symbol table of entry block
|
||||
initializeEntrySymbolTable(bbToISymTab[cfg.enter()]);
|
||||
// Initialize CfgMaps
|
||||
m_bbToISymTab = cfg.makeBlockMap<SymTab>();
|
||||
m_bbToOSymTab = cfg.makeBlockMap<SymTab>();
|
||||
m_bbToCondp = cfg.makeBlockMap<DfgVertex*>();
|
||||
|
||||
// Synthesize all blocks
|
||||
for (const V3GraphVertex& cfgVtx : cfg.vertices()) {
|
||||
const BasicBlock& bb = *cfgVtx.as<BasicBlock>();
|
||||
// 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<const CfgBlock&>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<const ControlFlowGraph> m_cfgp;
|
||||
const std::unique_ptr<CfgGraph> m_cfgp;
|
||||
// Vertices this logic was synthesized into. Excluding variables
|
||||
std::vector<DfgVertex*> m_synth;
|
||||
|
||||
|
|
@ -356,7 +356,7 @@ public:
|
|||
, m_nodep{nodep}
|
||||
, m_cfgp{nullptr} {}
|
||||
|
||||
DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr<const ControlFlowGraph> cfgp)
|
||||
DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr<CfgGraph> 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<DfgVertex*>& synth() { return m_synth; }
|
||||
const std::vector<DfgVertex*>& synth() const { return m_synth; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T_Element*>(m_headp); }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue