Internals: Manage DfgVertex user data via a typed map (#6356)

Add DfgUserMap as a handle around the one pointer worth of algorithm
specific 'user' storage in each DfgVertex. This reduces verbosity,
improves type safety and correctness. Also enables us to remove one
pointer from DfgVertex to reduce memory use. No functional change.
This commit is contained in:
Geza Lore 2025-09-02 22:21:24 +01:00 committed by GitHub
parent a6f26b85b3
commit e63ed0a931
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 516 additions and 441 deletions

View File

@ -223,20 +223,26 @@ void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
}
// Otherwise they will be moved
vtxp->nodep()->user2p(vtxp);
vtxp->m_userCnt = 0;
vtxp->m_graphp = this;
vtxp->m_userGeneration = 0;
#ifdef VL_DEBUG
vtxp->m_dfgp = this;
#endif
}
m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices);
// Process constants
for (DfgConst& vtx : otherp->m_constVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
}
m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices);
// Process operations
for (DfgVertex& vtx : otherp->m_opVertices) {
vtx.m_userCnt = 0;
vtx.m_graphp = this;
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
}
m_opVertices.splice(m_opVertices.end(), otherp->m_opVertices);
// Update graph sizes
@ -586,8 +592,8 @@ bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
return result >> 1;
}
V3Hash DfgVertex::hash() {
V3Hash& result = user<V3Hash>();
V3Hash DfgVertex::hash(DfgUserMap<V3Hash>& cache) {
V3Hash& result = cache[this];
if (!result.value()) {
V3Hash hash{selfHash()};
// Variables are defined by themselves, so there is no need to hash them further
@ -597,7 +603,7 @@ V3Hash DfgVertex::hash() {
hash += m_type;
hash += size();
foreachSource([&](DfgVertex& vtx) {
hash += vtx.hash();
hash += vtx.hash(cache);
return false;
});
}

View File

@ -54,10 +54,18 @@
#define VL_NOT_FINAL // This #define fixes broken code folding in the CLion IDE
#endif
// Can T be stored in the memory allocted for U?
template <typename T, typename U>
inline constexpr bool fitsSpaceAllocatedFor() {
return sizeof(T) <= sizeof(U) && alignof(T) <= alignof(U);
}
class DfgEdge;
class DfgVertex;
class DfgGraph;
class DfgVisitor;
template <typename T_User, bool = fitsSpaceAllocatedFor<T_User, void*>()>
class DfgUserMap;
// Specialization of std::hash for a std::pair<const DfgVertex*, const DfgVertex*> for use below
template <>
@ -191,8 +199,8 @@ class DfgVertex VL_NOT_FINAL {
friend class DfgGraph;
friend class DfgEdge;
friend class DfgVisitor;
using UserDataStorage = void*; // Storage allocated for user data
template <typename, bool>
friend class DfgUserMap;
// STATE
V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph
@ -201,10 +209,16 @@ class DfgVertex VL_NOT_FINAL {
FileLine* const m_filelinep; // Source location
AstNodeDType* m_dtypep; // Data type of the result of this vertex - mutable for efficiency
DfgGraph* m_graphp; // The containing DfgGraph
const VDfgType m_type; // Vertex type tag
uint32_t m_userCnt = 0; // User data generation number
UserDataStorage m_userDataStorage = nullptr; // User data storage
// The only way to access thes is via DfgUserMap, so mutable is appropriate,
// the map can change while the keys (DfgVertex) are const.
mutable uint32_t m_userGeneration = 0; // User data generation number
mutable void* m_userStorage = nullptr; // User data storage - one pointer worth
#ifdef VL_DEBUG
DfgGraph* m_dfgp = nullptr; // Graph this vertex belongs to
#endif
// METHODS
// Visitor accept method
@ -252,8 +266,10 @@ public:
FileLine* fileline() const { return m_filelinep; }
// The data type of the result of the vertex
AstNodeDType* dtypep() const { return m_dtypep; }
// Is it a packed type (instead of an array)
// Is it a packed type
bool isPacked() const { return VN_IS(dtypep(), BasicDType); }
// Is it an array type
bool isArray() const { return !isPacked(); }
// Width of result
uint32_t width() const {
UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'");
@ -265,19 +281,6 @@ public:
return VN_AS(dtypep(), UnpackArrayDType)->elementsConst();
}
// Retrieve user data, constructing it fresh on first try.
template <typename T_User>
T_User& user();
// Retrieve user data, must be current.
template <typename T_User>
const T_User& getUser() const;
// Retrieve user data, must be current.
template <typename T_User>
T_User& getUser();
// Set user data, becomes current.
template <typename T_User>
void setUser(T_User value);
// Cache type for 'equals' below
using EqualsCache = std::unordered_map<std::pair<const DfgVertex*, const DfgVertex*>, uint8_t>;
@ -295,8 +298,8 @@ public:
}
// Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex).
// Uses user data for caching hashes
V3Hash hash() VL_MT_DISABLED;
// Uses the given DfgUserMap for caching hashes
V3Hash hash(DfgUserMap<V3Hash>& cache) VL_MT_DISABLED;
// Predicate: has 1 or more sinks
bool hasSinks() const { return !m_sinks.empty(); }
@ -465,34 +468,7 @@ public:
//------------------------------------------------------------------------------
// Dataflow graph
class DfgGraph final {
friend class DfgVertex;
// TYPES
// RAII handle for DfgVertex user data
class UserDataInUse final {
DfgGraph* m_graphp; // The referenced graph
public:
// cppcheck-suppress noExplicitConstructor
UserDataInUse(DfgGraph* graphp)
: m_graphp{graphp} {}
// cppcheck-suppress noExplicitConstructor
UserDataInUse(UserDataInUse&& that) {
UASSERT(that.m_graphp, "Moving from empty");
m_graphp = std::exchange(that.m_graphp, nullptr);
}
VL_UNCOPYABLE(UserDataInUse);
UserDataInUse& operator=(UserDataInUse&& that) {
UASSERT(that.m_graphp, "Moving from empty");
m_graphp = std::exchange(that.m_graphp, nullptr);
return *this;
}
~UserDataInUse() {
if (m_graphp) m_graphp->m_userCurrent = 0;
}
};
friend class DfgUserMapBase;
// MEMBERS
@ -503,16 +479,18 @@ class DfgGraph final {
DfgVertex::List<DfgVertexVar> m_varVertices; // The variable vertices in the graph
DfgVertex::List<DfgConst> m_constVertices; // The constant vertices in the graph
DfgVertex::List<DfgVertex> m_opVertices; // The operation vertices in the graph
size_t m_size = 0; // Number of vertices in the graph
uint32_t m_userCurrent = 0; // Vertex user data generation number currently in use
uint32_t m_userCnt = 0; // Vertex user data generation counter
// Parent of the graph (i.e.: the module containing the logic represented by this graph),
// or nullptr when run after V3Scope
AstModule* const m_modulep;
const std::string m_name; // Name of graph - need not be unique
std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy
// The only way to access thes is via DfgUserMap, so mutable is appropriate,
// the map can change while the graph is const.
mutable bool m_vertexUserInUse = false; // Vertex user data currently in use
mutable uint32_t m_vertexUserGeneration = 0; // Vertex user data generation counter
public:
// CONSTRUCTOR
explicit DfgGraph(AstModule* modulep, const string& name = "") VL_MT_DISABLED;
@ -528,14 +506,9 @@ public:
// Name of this graph
const string& name() const { return m_name; }
// Reset Vertex user data
UserDataInUse userDataInUse() {
UASSERT(!m_userCurrent, "Conflicting use of DfgVertex user data");
++m_userCnt;
UASSERT(m_userCnt, "'m_userCnt' overflow");
m_userCurrent = m_userCnt;
return UserDataInUse{this};
}
// Create a new DfgUserMap
template <typename T_User>
inline DfgUserMap<T_User> makeUserMap() const;
// Access to vertex lists
DfgVertex::List<DfgVertexVar>& varVertices() { return m_varVertices; }
@ -547,6 +520,9 @@ public:
// Add DfgVertex to this graph (assumes not yet contained).
void addVertex(DfgVertex& vtx) {
#ifdef VL_DEBUG
UASSERT_OBJ(!vtx.m_dfgp, &vtx, "Vertex already in a graph");
#endif
// Note: changes here need to be replicated in DfgGraph::mergeGraphs
++m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
@ -556,12 +532,17 @@ public:
} else {
m_opVertices.linkBack(&vtx);
}
vtx.m_userCnt = 0;
vtx.m_graphp = this;
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
}
// Remove DfgVertex form this graph (assumes it is contained).
void removeVertex(DfgVertex& vtx) {
#ifdef VL_DEBUG
UASSERT_OBJ(vtx.m_dfgp == this, &vtx, "Vertex not in this graph");
#endif
// Note: changes here need to be replicated in DfgGraph::mergeGraphs
--m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
@ -571,8 +552,10 @@ public:
} else {
m_opVertices.unlink(&vtx);
}
vtx.m_userCnt = 0;
vtx.m_graphp = nullptr;
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = nullptr;
#endif
}
// Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices
@ -655,6 +638,137 @@ public:
sinkCone(const std::vector<const DfgVertex*>&) const VL_MT_DISABLED;
};
//------------------------------------------------------------------------------
// Map from DfgVertices to T_Value implemeneted via DfgVertex::m_userStorage
// Base class with common behavour
class DfgUserMapBase VL_NOT_FINAL {
template <typename, bool>
friend class DfgUserMap;
protected:
// STATE
const DfgGraph* m_dfgp; // The graph this map is for
// The current generation number
const uint32_t m_currentGeneration;
// CONSTRUCTOR
explicit DfgUserMapBase(const DfgGraph* dfgp)
: m_dfgp{dfgp}
, m_currentGeneration{++m_dfgp->m_vertexUserGeneration} {
UASSERT(m_currentGeneration, "DfgGraph user data genartion number overflow");
UASSERT(!m_dfgp->m_vertexUserInUse, "DfgUserMap already in use for this DfgGraph");
m_dfgp->m_vertexUserInUse = true;
}
VL_UNCOPYABLE(DfgUserMapBase);
DfgUserMapBase(DfgUserMapBase&& that)
: m_dfgp{that.m_dfgp}
, m_currentGeneration{that.m_currentGeneration} {
that.m_dfgp = nullptr;
}
DfgUserMapBase& operator=(DfgUserMapBase&&) = delete;
public:
~DfgUserMapBase() {
if (m_dfgp) m_dfgp->m_vertexUserInUse = false;
}
};
// Specialization where T_Value fits in DfgVertex::m_userStorage directly
template <typename T_Value>
class DfgUserMap<T_Value, true> final : public DfgUserMapBase {
static_assert(fitsSpaceAllocatedFor<T_Value, decltype(DfgVertex::m_userStorage)>(),
"'T_Value' does not fit 'DfgVertex::m_userStorage'");
friend class DfgGraph;
// CONSTRUCTOR
explicit DfgUserMap(const DfgGraph* dfgp)
: DfgUserMapBase{dfgp} {}
VL_UNCOPYABLE(DfgUserMap);
DfgUserMap& operator=(DfgUserMap&&) = delete;
public:
DfgUserMap(DfgUserMap&&) = default;
~DfgUserMap() = default;
// METHODS
// Retrieve mapped value for 'vtx', value initializing it on first access
T_Value& operator[](const DfgVertex& vtx) {
#ifdef VL_DEBUG
UASSERT_OBJ(vtx.m_dfgp == m_dfgp, &vtx, "Vertex not in this graph");
#endif
T_Value* const storagep = reinterpret_cast<T_Value*>(&vtx.m_userStorage);
if (vtx.m_userGeneration != m_currentGeneration) {
new (storagep) T_Value{};
vtx.m_userGeneration = m_currentGeneration;
}
return *storagep;
}
// Same as above with pointer as key
T_Value& operator[](const DfgVertex* vtxp) { return (*this)[*vtxp]; }
// Retrieve mapped value of 'vtx', must be alerady present
T_Value& at(const DfgVertex& vtx) const {
#ifdef VL_DEBUG
UASSERT_OBJ(vtx.m_dfgp == m_dfgp, &vtx, "Vertex not in this graph");
#endif
UASSERT_OBJ(vtx.m_userGeneration == m_currentGeneration, &vtx, "Vertex not in map");
T_Value* const storagep = reinterpret_cast<T_Value*>(&vtx.m_userStorage);
return *storagep;
}
// Same as above with pointer as key
T_Value& at(const DfgVertex* vtxp) const { return (*this).at(*vtxp); }
};
// Specialization where T_Value does not fit in DfgVertex::m_userStorage directly
template <typename T_Value>
class DfgUserMap<T_Value, false> final : public DfgUserMapBase {
static_assert(fitsSpaceAllocatedFor<T_Value*, decltype(DfgVertex::m_userStorage)>(),
"'T_Value*' does not fit 'DfgVertex::m_userStorage'");
friend class DfgGraph;
// STATE
std::deque<T_Value> m_storage; // Storage for T_Value instances
// CONSTRUCTOR
explicit DfgUserMap(const DfgGraph* dfgp)
: DfgUserMapBase{dfgp} {}
VL_UNCOPYABLE(DfgUserMap);
DfgUserMap& operator=(DfgUserMap&&) = delete;
public:
DfgUserMap(DfgUserMap&&) = default;
~DfgUserMap() = default;
// METHODS
// Retrieve mapped value for 'vtx', value initializing it on first access
T_Value& operator[](const DfgVertex& vtx) {
#ifdef VL_DEBUG
UASSERT_OBJ(vtx.m_dfgp == m_dfgp, &vtx, "Vertex not in this graph");
#endif
T_Value*& storagepr = reinterpret_cast<T_Value*&>(vtx.m_userStorage);
if (vtx.m_userGeneration != m_currentGeneration) {
m_storage.emplace_back();
storagepr = &m_storage.back();
vtx.m_userGeneration = m_currentGeneration;
}
return *storagepr;
}
// Same as above with pointer as key
T_Value& operator[](const DfgVertex* vtxp) { return (*this)[*vtxp]; }
// Retrieve mapped value of 'vtx', must be alerady present
T_Value& at(const DfgVertex& vtx) const {
#ifdef VL_DEBUG
UASSERT_OBJ(vtx.m_dfgp == m_dfgp, &vtx, "Vertex not in this graph");
#endif
UASSERT_OBJ(vtx.m_userGeneration == m_currentGeneration, &vtx, "Vertex not in map");
return *reinterpret_cast<T_Value*&>(vtx.m_userStorage);
}
// Same as above with pointer as key
T_Value& at(const DfgVertex* vtxp) const { return (*this).at(*vtxp); }
};
//------------------------------------------------------------------------------
// Inline method definitions
@ -684,57 +798,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
// }}}
// DfgVertex {{{
// DfgGraph {{{
template <typename T_User>
T_User& DfgVertex::user() {
static_assert(sizeof(T_User) <= sizeof(UserDataStorage),
"Size of user data type 'T_User' is too large for allocated storage");
static_assert(alignof(T_User) <= alignof(UserDataStorage),
"Alignment of user data type 'T_User' is larger than allocated storage");
T_User* const storagep = reinterpret_cast<T_User*>(&m_userDataStorage);
const uint32_t userCurrent = m_graphp->m_userCurrent;
UDEBUGONLY(UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving"););
if (m_userCnt != userCurrent) {
m_userCnt = userCurrent;
new (storagep) T_User{};
}
return *storagep;
}
template <typename T_User>
const T_User& DfgVertex::getUser() const {
static_assert(sizeof(T_User) <= sizeof(UserDataStorage),
"Size of user data type 'T_User' is too large for allocated storage");
static_assert(alignof(T_User) <= alignof(UserDataStorage),
"Alignment of user data type 'T_User' is larger than allocated storage");
const T_User* const storagep = reinterpret_cast<const T_User*>(&m_userDataStorage);
#if VL_DEBUG
const uint32_t userCurrent = m_graphp->m_userCurrent;
UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving");
UASSERT_OBJ(m_userCnt == userCurrent, this, "DfgVertex user data is stale");
#endif
return *storagep;
}
template <typename T_User>
T_User& DfgVertex::getUser() {
return const_cast<T_User&>(const_cast<const DfgVertex*>(this)->getUser<T_User>());
}
template <typename T_User>
void DfgVertex::setUser(T_User value) {
static_assert(sizeof(T_User) <= sizeof(UserDataStorage),
"Size of user data type 'T_User' is too large for allocated storage");
static_assert(alignof(T_User) <= alignof(UserDataStorage),
"Alignment of user data type 'T_User' is larger than allocated storage");
T_User* const storagep = reinterpret_cast<T_User*>(&m_userDataStorage);
const uint32_t userCurrent = m_graphp->m_userCurrent;
#if VL_DEBUG
UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving");
#endif
m_userCnt = userCurrent;
*storagep = value;
DfgUserMap<T_User> DfgGraph::makeUserMap() const {
return DfgUserMap<T_User>{this};
}
// }}}

View File

@ -29,6 +29,9 @@
VL_DEFINE_DEBUG_FUNCTIONS;
// Throughout these algorithm, we use the DfgUserMap as a map to the SCC number
using Vtx2Scc = DfgUserMap<uint64_t>;
class TraceDriver final : public DfgVisitor {
// TYPES
@ -63,6 +66,7 @@ class TraceDriver final : public DfgVisitor {
// STATE
DfgGraph& m_dfg; // The graph being processed
Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map
// The strongly connected component we are trying to escape
const uint64_t m_component;
const bool m_aggressive; // Trace aggressively, creating intermediate ops
@ -110,7 +114,7 @@ class TraceDriver final : public DfgVisitor {
if VL_CONSTEXPR_CXX17 (std::is_same<DfgConst, Vertex>::value) {
DfgConst* const vtxp = new DfgConst{m_dfg, refp->fileline(), width, 0};
vtxp->template setUser<uint64_t>(0);
m_vtx2Scc[vtxp] = 0;
m_newVtxps.emplace_back(vtxp);
return reinterpret_cast<Vertex*>(vtxp);
} else {
@ -121,7 +125,7 @@ class TraceDriver final : public DfgVisitor {
Vertex>::type;
AstNodeDType* const dtypep = V3Dfg::dtypePacked(width);
Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), dtypep};
vtxp->template setUser<uint64_t>(0);
m_vtx2Scc[vtxp] = 0;
m_newVtxps.emplace_back(vtxp);
return reinterpret_cast<Vertex*>(vtxp);
}
@ -163,7 +167,7 @@ class TraceDriver final : public DfgVisitor {
// found what we were looking for. However, keep going past a splice,
// or a unit as they cannot be use directly (they must always feed into
// a variable, so we can't make them drive arbitrary logic)
if (vtxp->getUser<uint64_t>() != m_component //
if (m_vtx2Scc[vtxp] != m_component //
&& !vtxp->is<DfgVertexSplice>() //
&& !vtxp->is<DfgUnitArray>()) {
if (msb != vtxp->width() - 1 || lsb != 0) {
@ -614,8 +618,9 @@ class TraceDriver final : public DfgVisitor {
#undef SET_RESULT
// CONSTRUCTOR
TraceDriver(DfgGraph& dfg, uint64_t component, bool aggressive)
TraceDriver(DfgGraph& dfg, Vtx2Scc& vtx2Scc, uint64_t component, bool aggressive)
: m_dfg{dfg}
, m_vtx2Scc{vtx2Scc}
, m_component{component}
, m_aggressive{aggressive} {
#ifdef VL_DEBUG
@ -629,18 +634,18 @@ class TraceDriver final : public DfgVisitor {
}
public:
// Given a Vertex that is part of an SCC denoted by vtxp->user<uint64_t>(),
// Given a Vertex that is part of an SCC denoted by vtx2Scc,
// return a vertex that is equivalent to 'vtxp[lsb +: width]', but is not
// part of the same SCC. Returns nullptr if such a vertex cannot be
// computed. This can add new vertices to the graph. The 'aggressive' flag
// enables creating many intermediate operations. This should only be set
// if it is reasonably certain the tracing will succeed, otherwise we can
// waste a lot of compute.
static DfgVertex* apply(DfgGraph& dfg, DfgVertex* vtxp, uint32_t lsb, uint32_t width,
bool aggressive) {
TraceDriver traceDriver{dfg, vtxp->getUser<uint64_t>(), aggressive};
static DfgVertex* apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc, DfgVertex& vtx, uint32_t lsb,
uint32_t width, bool aggressive) {
TraceDriver traceDriver{dfg, vtx2Scc, vtx2Scc[vtx], aggressive};
// Find the out-of-component driver of the given vertex
DfgVertex* const resultp = traceDriver.trace(vtxp, lsb + width - 1, lsb);
DfgVertex* const resultp = traceDriver.trace(&vtx, lsb + width - 1, lsb);
// Delete unused newly created vertices (these can be created if a
// partial trace succeded, but an eventual one falied). Because new
// vertices should be created depth first, it is enough to do a single
@ -660,6 +665,7 @@ public:
class IndependentBits final : public DfgVisitor {
// STATE
const Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map
const uint64_t m_component; // The component the start vertex is part of
// Vertex to current bit mask map. The mask is set for the bits that **depend** on 'm_varp'.
std::unordered_map<const DfgVertex*, V3Number> m_vtxp2Mask;
@ -675,9 +681,7 @@ class IndependentBits final : public DfgVisitor {
std::forward_as_tuple(vtxp), //
std::forward_as_tuple(vtxp->fileline(), static_cast<int>(vtxp->width()), 0));
// Initialize to all ones if the vertex is part of the same component, otherwise zeroes
if (pair.second && vtxp->getUser<uint64_t>() == m_component) {
pair.first->second.setAllBits1();
}
if (pair.second && m_vtx2Scc.at(vtxp) == m_component) { pair.first->second.setAllBits1(); }
return pair.first->second;
}
@ -910,8 +914,9 @@ class IndependentBits final : public DfgVisitor {
#undef MASK
// CONSTRUCTOR
IndependentBits(DfgGraph& dfg, DfgVertex* vtxp)
: m_component{vtxp->getUser<uint64_t>()} {
IndependentBits(DfgGraph& dfg, const Vtx2Scc& vtx2Scc, DfgVertex& vertex)
: m_vtx2Scc{vtx2Scc}
, m_component{m_vtx2Scc.at(vertex)} {
#ifdef VL_DEBUG
if (v3Global.opt.debugCheck()) {
@ -927,7 +932,7 @@ class IndependentBits final : public DfgVisitor {
// Enqueue every operation vertex in the analysed component
for (DfgVertex& vtx : dfg.opVertices()) {
if (vtx.getUser<uint64_t>() == m_component) workList.emplace_back(&vtx);
if (m_vtx2Scc.at(vtx) == m_component) workList.emplace_back(&vtx);
}
// While there is an item on the worklist ...
@ -940,7 +945,7 @@ class IndependentBits final : public DfgVisitor {
// For an unpacked array vertex, just enque it's sinks.
// (There can be no loops through arrays directly)
currp->foreachSink([&](DfgVertex& vtx) {
if (vtx.getUser<uint64_t>() == m_component) workList.emplace_back(&vtx);
if (m_vtx2Scc.at(vtx) == m_component) workList.emplace_back(&vtx);
return false;
});
continue;
@ -957,7 +962,7 @@ class IndependentBits final : public DfgVisitor {
// If mask changed, enqueue sinks
if (!prevMask.isCaseEq(maskCurr)) {
currp->foreachSink([&](DfgVertex& vtx) {
if (vtx.getUser<uint64_t>() == m_component) workList.emplace_back(&vtx);
if (m_vtx2Scc.at(vtx) == m_component) workList.emplace_back(&vtx);
return false;
});
@ -981,62 +986,78 @@ public:
// independent of the vertex itself (logic is not circular). The result is
// a conservative estimate, so bits reported dependent might not actually
// be, but all bits reported independent are known to be so.
static V3Number apply(DfgGraph& dfg, DfgVertex* vtxp) {
IndependentBits independentBits{dfg, vtxp};
static V3Number apply(DfgGraph& dfg, const Vtx2Scc& vtx2Scc, DfgVertex& vtx) {
IndependentBits independentBits{dfg, vtx2Scc, vtx};
// The mask represents the dependent bits, so invert it
V3Number result{vtxp->fileline(), static_cast<int>(vtxp->width()), 0};
result.opNot(independentBits.mask(vtxp));
V3Number result{vtx.fileline(), static_cast<int>(vtx.width()), 0};
result.opNot(independentBits.mask(&vtx));
return result;
}
};
class FixUpSelDrivers final {
static size_t fixUpSelSinks(DfgGraph& dfg, DfgVertex* vtxp) {
size_t nImprovements = 0;
const uint64_t component = vtxp->getUser<uint64_t>();
vtxp->foreachSink([&](DfgVertex& sink) {
DfgGraph& m_dfg; // The graph being processed
Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map
size_t m_nImprovements = 0; // Number of improvements mde
void fixUpSelSinks(DfgVertex& vtx) {
const uint64_t component = m_vtx2Scc[vtx];
vtx.foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of same cycle
if (sink.getUser<uint64_t>() != component) return false;
if (m_vtx2Scc[sink] != component) return false;
// Only handle Sel
DfgSel* const selp = sink.cast<DfgSel>();
if (!selp) return false;
// Try to find the driver of the selected bits outside the cycle
DfgVertex* const fixp
= TraceDriver::apply(dfg, vtxp, selp->lsb(), selp->width(), false);
= TraceDriver::apply(m_dfg, m_vtx2Scc, vtx, selp->lsb(), selp->width(), false);
if (!fixp) return false;
// Found an out-of-cycle driver. We can replace this sel with that.
selp->replaceWith(fixp);
selp->unlinkDelete(dfg);
++nImprovements;
selp->unlinkDelete(m_dfg);
++m_nImprovements;
return false;
});
return nImprovements;
}
static size_t fixUpArraySelSinks(DfgGraph& dfg, DfgVertex* vtxp) {
size_t nImprovements = 0;
const uint64_t component = vtxp->getUser<uint64_t>();
vtxp->foreachSink([&](DfgVertex& sink) {
void fixUpArraySelSinks(DfgVertex& vtx) {
const uint64_t component = m_vtx2Scc[vtx];
vtx.foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of same cycle
if (sink.getUser<uint64_t>() != component) return false;
if (m_vtx2Scc[sink] != component) return false;
// Only handle ArraySels
DfgArraySel* const aselp = sink.cast<DfgArraySel>();
if (!aselp) return false;
DfgArraySel* const asp = sink.cast<DfgArraySel>();
if (!asp) return false;
// First, try to fix up the whole word
if (DfgVertex* const fixp = TraceDriver::apply(dfg, aselp, 0, aselp->width(), false)) {
DfgVertex* const fixp
= TraceDriver::apply(m_dfg, m_vtx2Scc, *asp, 0, asp->width(), false);
if (fixp) {
// Found an out-of-cycle driver for the whole ArraySel
aselp->replaceWith(fixp);
aselp->unlinkDelete(dfg);
++nImprovements;
asp->replaceWith(fixp);
asp->unlinkDelete(m_dfg);
++m_nImprovements;
return false;
}
// Attempt to fix up piece-wise at Sels applied to the ArraySel
nImprovements += fixUpSelSinks(dfg, aselp);
fixUpSelSinks(*asp);
// Remove if became unused
if (!aselp->hasSinks()) aselp->unlinkDelete(dfg);
if (!asp->hasSinks()) asp->unlinkDelete(m_dfg);
return false;
});
return nImprovements;
}
FixUpSelDrivers(DfgGraph& dfg, Vtx2Scc& vtx2Scc, DfgVertexVar& var)
: m_dfg{dfg}
, m_vtx2Scc{vtx2Scc} {
UINFO(9, "FixUpSelDrivers of " << var.nodep()->name());
if (var.isPacked()) {
// For Packed variables, fix up all Sels applied to it
fixUpSelSinks(var);
} else if (var.isArray()) {
// For Array variables, fix up each ArraySel applied to it
fixUpArraySelSinks(var);
}
UINFO(9, "FixUpSelDrivers made " << m_nImprovements << " improvements");
}
public:
@ -1044,26 +1065,20 @@ public:
// expression of the selected bits. This can fix things like
// 'a[1:0] = foo', 'a[2] = a[1]', which are somewhat common.
// Returns the number of drivers fixed up.
static size_t apply(DfgGraph& dfg, DfgVertexVar* varp) {
UINFO(9, "FixUpSelDrivers of " << varp->nodep()->name());
size_t nImprovements = 0;
if (varp->is<DfgVarPacked>()) {
// For Packed variables, fix up all Sels applied to it
nImprovements += fixUpSelSinks(dfg, varp);
} else if (varp->is<DfgVarArray>()) {
// For Array variables, fix up each ArraySel applied to it
nImprovements += fixUpArraySelSinks(dfg, varp);
}
UINFO(9, "FixUpSelDrivers made " << nImprovements << " improvements");
return nImprovements;
static size_t apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc, DfgVertexVar& var) {
return FixUpSelDrivers{dfg, vtx2Scc, var}.m_nImprovements;
}
};
class FixUpIndependentRanges final {
// Returns a bitmask set if that bit of 'vtxp' is used (has a sink)
static V3Number computeUsedBits(DfgVertex* vtxp) {
V3Number result{vtxp->fileline(), static_cast<int>(vtxp->width()), 0};
vtxp->foreachSink([&result](DfgVertex& sink) {
DfgGraph& m_dfg; // The graph being processed
Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map
size_t m_nImprovements = 0; // Number of improvements mde
// Returns a bitmask set if that bit of 'vtx' is used (has a sink)
static V3Number computeUsedBits(DfgVertex& vtx) {
V3Number result{vtx.fileline(), static_cast<int>(vtx.width()), 0};
vtx.foreachSink([&result](DfgVertex& sink) {
// If used via a Sel, mark the selected bits used
if (const DfgSel* const selp = sink.cast<DfgSel>()) {
uint32_t lsb = selp->lsb();
@ -1078,24 +1093,22 @@ class FixUpIndependentRanges final {
return result;
}
static std::string debugStr(const DfgVertex* vtxp) {
if (const DfgArraySel* const aselp = vtxp->cast<DfgArraySel>()) {
static std::string debugStr(const DfgVertex& vtx) {
if (const DfgArraySel* const aselp = vtx.cast<DfgArraySel>()) {
const size_t i = aselp->bitp()->as<DfgConst>()->toSizeT();
return debugStr(aselp->fromp()) + "[" + std::to_string(i) + "]";
return debugStr(*aselp->fromp()) + "[" + std::to_string(i) + "]";
}
if (const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>()) {
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
return varp->nodep()->name();
}
vtxp->v3fatalSrc("Unhandled node type"); // LCOV_EXCL_LINE
vtx.v3fatalSrc("Unhandled node type"); // LCOV_EXCL_LINE
}
// Trace drivers of independent bits of 'vtxp' in the range '[hi:lo]'
// append replacement terms to 'termps'. Returns number of successful
// replacements.
static size_t gatherTerms(std::vector<DfgVertex*>& termps, DfgGraph& dfg,
DfgVertex* const vtxp, const V3Number& indpBits, const uint32_t hi,
const uint32_t lo) {
size_t nImprovements = 0;
void gatherTerms(std::vector<DfgVertex*>& termps, DfgVertex& vtx, const V3Number& indpBits,
const uint32_t hi, const uint32_t lo) {
// Iterate through consecutive dependent/non-dependet ranges within [hi:lo]
bool isIndependent = indpBits.bitIs1(lo);
uint32_t lsb = lo;
@ -1106,20 +1119,20 @@ class FixUpIndependentRanges final {
DfgVertex* termp = nullptr;
// If the range is not self-dependent, attempt to trace its driver
if (isIndependent) {
termp = TraceDriver::apply(dfg, vtxp, lsb, width, true);
termp = TraceDriver::apply(m_dfg, m_vtx2Scc, vtx, lsb, width, true);
if (termp) {
++nImprovements;
++m_nImprovements;
} else {
UINFO(5, "TraceDriver of independent range failed for "
<< debugStr(vtxp) << "[" << msb << ":" << lsb << "]");
<< debugStr(vtx) << "[" << msb << ":" << lsb << "]");
}
}
// Fall back on using the part of the variable (if dependent, or trace failed)
if (!termp) {
AstNodeDType* const dtypep = V3Dfg::dtypePacked(width);
DfgSel* const selp = new DfgSel{dfg, vtxp->fileline(), dtypep};
DfgSel* const selp = new DfgSel{m_dfg, vtx.fileline(), dtypep};
// Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp'
selp->setUser<uint64_t>(vtxp->getUser<uint64_t>());
m_vtx2Scc[selp] = m_vtx2Scc.at(vtx);
// Do not connect selp->fromp yet, need to do afer replacing 'vtxp'
selp->lsb(lsb);
termp = selp;
@ -1130,40 +1143,38 @@ class FixUpIndependentRanges final {
isIndependent = !isIndependent;
lsb = msb + 1;
}
return nImprovements;
}
static size_t fixUpPacked(DfgGraph& dfg, DfgVertex* vtxp) {
UASSERT_OBJ(VN_IS(vtxp->dtypep(), BasicDType), vtxp, "Should be a packed BasicDType");
size_t nImprovements = 0;
void fixUpPacked(DfgVertex& vtx) {
UASSERT_OBJ(vtx.isPacked(), &vtx, "Should be a packed type");
// Figure out which bits of 'vtxp' are used
const V3Number usedBits = computeUsedBits(vtxp);
UINFO(9, "Used bits of '" << debugStr(vtxp) << "' are "
<< usedBits.displayed(vtxp->fileline(), "%b"));
const V3Number usedBits = computeUsedBits(vtx);
UINFO(9, "Used bits of '" << debugStr(vtx) << "' are "
<< usedBits.displayed(vtx.fileline(), "%b"));
// Nothing to do if no bits are used
if (usedBits.isEqZero()) return 0;
if (usedBits.isEqZero()) return;
// Figure out which bits of 'vtxp' are dependent of themselves
const V3Number indpBits = IndependentBits::apply(dfg, vtxp);
UINFO(9, "Independent bits of '" << debugStr(vtxp) << "' are "
<< indpBits.displayed(vtxp->fileline(), "%b"));
const V3Number indpBits = IndependentBits::apply(m_dfg, m_vtx2Scc, vtx);
UINFO(9, "Independent bits of '" << debugStr(vtx) << "' are "
<< indpBits.displayed(vtx.fileline(), "%b"));
// Can't do anything if all bits are dependent
if (indpBits.isEqZero()) return 0;
if (indpBits.isEqZero()) return;
{
// Nothing to do if no used bits are independen (all used bits are dependent)
V3Number usedAndIndependent{vtxp->fileline(), static_cast<int>(vtxp->width()), 0};
V3Number usedAndIndependent{vtx.fileline(), static_cast<int>(vtx.width()), 0};
usedAndIndependent.opAnd(usedBits, indpBits);
if (usedAndIndependent.isEqZero()) return 0;
if (usedAndIndependent.isEqZero()) return;
}
// We are computing the terms to concatenate and replace 'vtxp' with
std::vector<DfgVertex*> termps;
// Iterate through consecutive used/unused ranges
FileLine* const flp = vtxp->fileline();
const uint32_t width = vtxp->width();
FileLine* const flp = vtx.fileline();
const uint32_t width = vtx.width();
bool isUsed = usedBits.bitIs1(0); // Is current range used
uint32_t lsb = 0; // LSB of current range
for (uint32_t msb = 0; msb < width; ++msb) {
@ -1171,11 +1182,11 @@ class FixUpIndependentRanges final {
if (!endRange) continue;
if (isUsed) {
// The range is used, compute the replacement terms
nImprovements += gatherTerms(termps, dfg, vtxp, indpBits, msb, lsb);
gatherTerms(termps, vtx, indpBits, msb, lsb);
} else {
// The range is not used, just use constant 0 as a placeholder
DfgConst* const constp = new DfgConst{dfg, flp, msb - lsb + 1, 0};
constp->setUser<uint64_t>(0);
DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0};
m_vtx2Scc[constp] = 0;
termps.emplace_back(constp);
}
// Next iteration
@ -1184,21 +1195,21 @@ class FixUpIndependentRanges final {
}
// If no imporovement was possible, delete the terms we just created
if (!nImprovements) {
for (DfgVertex* const termp : termps) VL_DO_DANGLING(termp->unlinkDelete(dfg), termp);
if (!m_nImprovements) {
for (DfgVertex* const tp : termps) VL_DO_DANGLING(tp->unlinkDelete(m_dfg), tp);
termps.clear();
return 0;
return;
}
// We managed to imporove something, apply the replacement
// Concatenate all the terms to create the replacement
DfgVertex* replacementp = termps.front();
const uint64_t vComp = vtxp->getUser<uint64_t>();
const uint64_t vComp = m_vtx2Scc.at(vtx);
for (size_t i = 1; i < termps.size(); ++i) {
DfgVertex* const termp = termps[i];
const uint32_t catWidth = replacementp->width() + termp->width();
AstNodeDType* const dtypep = V3Dfg::dtypePacked(catWidth);
DfgConcat* const catp = new DfgConcat{dfg, flp, dtypep};
DfgConcat* const catp = new DfgConcat{m_dfg, flp, dtypep};
catp->rhsp(replacementp);
catp->lhsp(termp);
// Need to figure out which component the replacement vertex
@ -1206,56 +1217,61 @@ class FixUpIndependentRanges final {
// component, it's part of that component, otherwise its not
// cyclic (all terms are from outside the original component,
// and feed into the original component).
const uint64_t tComp = termp->getUser<uint64_t>();
const uint64_t rComp = replacementp->getUser<uint64_t>();
catp->setUser<uint64_t>(tComp == vComp || rComp == vComp ? vComp : 0);
const uint64_t tComp = m_vtx2Scc.at(termp);
const uint64_t rComp = m_vtx2Scc.at(replacementp);
m_vtx2Scc[catp] = tComp == vComp || rComp == vComp ? vComp : 0;
replacementp = catp;
}
// Replace the vertex
vtxp->replaceWith(replacementp);
vtx.replaceWith(replacementp);
// Connect the Sel nodes in the replacement
for (DfgVertex* const termp : termps) {
if (DfgSel* const selp = termp->cast<DfgSel>()) {
if (!selp->fromp()) selp->fromp(vtxp);
if (!selp->fromp()) selp->fromp(&vtx);
}
}
}
// Done
return nImprovements;
void main(DfgVertexVar& var) {
UINFO(9, "FixUpIndependentRanges of " << var.nodep()->name());
if (var.is<DfgVarPacked>()) {
// For Packed variables, fix up as whole
fixUpPacked(var);
} else if (var.is<DfgVarArray>()) {
// For array variables, fix up element-wise
const uint64_t component = m_vtx2Scc.at(var);
var.foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of same cycle
if (m_vtx2Scc.at(sink) != component) return false;
// Only handle ArraySels with constant index
DfgArraySel* const aselp = sink.cast<DfgArraySel>();
if (!aselp) return false;
if (!aselp->bitp()->is<DfgConst>()) return false;
// Fix up the word
fixUpPacked(*aselp);
// Remove if became unused
if (!aselp->hasSinks()) aselp->unlinkDelete(m_dfg);
return false;
});
}
UINFO(9, "FixUpIndependentRanges made " << m_nImprovements << " improvements");
}
FixUpIndependentRanges(DfgGraph& dfg, Vtx2Scc& vtx2Scc, DfgVertexVar& var)
: m_dfg{dfg}
, m_vtx2Scc{vtx2Scc} {
main(var);
}
public:
// Similar to FixUpSelDrivers, but first comptute which bits of the
// variable are self dependent, and fix up those that are independent
// but used.
static size_t apply(DfgGraph& dfg, DfgVertexVar* varp) {
UINFO(9, "FixUpIndependentRanges of " << varp->nodep()->name());
size_t nImprovements = 0;
if (varp->is<DfgVarPacked>()) {
// For Packed variables, fix up as whole
nImprovements += fixUpPacked(dfg, varp);
} else if (varp->is<DfgVarArray>()) {
// For array variables, fix up element-wise
const uint64_t component = varp->getUser<uint64_t>();
varp->foreachSink([&](DfgVertex& sink) {
// Ignore if sink is not part of same cycle
if (sink.getUser<uint64_t>() != component) return false;
// Only handle ArraySels with constant index
DfgArraySel* const aselp = sink.cast<DfgArraySel>();
if (!aselp) return false;
if (!aselp->bitp()->is<DfgConst>()) return false;
// Fix up the word
nImprovements += fixUpPacked(dfg, aselp);
// Remove if became unused
if (!aselp->hasSinks()) aselp->unlinkDelete(dfg);
return false;
});
}
UINFO(9, "FixUpIndependentRanges made " << nImprovements << " improvements");
return nImprovements;
static size_t apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc, DfgVertexVar& var) {
return FixUpIndependentRanges{dfg, vtx2Scc, var}.m_nImprovements;
}
};
@ -1292,8 +1308,9 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Iterate while an improvement can be made and the graph is still cyclic
do {
// Color SCCs (populates DfgVertex::user<uint64_t>())
const auto userDataInUse = res.userDataInUse();
const uint32_t numNonTrivialSCCs = V3DfgPasses::colorStronglyConnectedComponents(res);
Vtx2Scc vtx2Scc = res.makeUserMap<uint64_t>();
const uint32_t numNonTrivialSCCs
= V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc);
// Congrats if it has become acyclic
if (!numNonTrivialSCCs) {
@ -1310,10 +1327,9 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Method 1: FixUpSelDrivers
for (DfgVertexVar& vtx : res.varVertices()) {
// If Variable is not part of a cycle, move on
const uint64_t component = vtx.getUser<uint64_t>();
if (!component) continue;
if (!vtx2Scc[vtx]) continue;
const size_t nFixed = FixUpSelDrivers::apply(res, &vtx);
const size_t nFixed = FixUpSelDrivers::apply(res, vtx2Scc, vtx);
if (nFixed) {
nImprovements += nFixed;
ctx.m_breakCyclesContext.m_nImprovements += nFixed;
@ -1327,10 +1343,9 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
// Method 2. FixUpIndependentRanges
for (DfgVertexVar& vtx : res.varVertices()) {
// If Variable is not part of a cycle, move on
const uint64_t component = vtx.getUser<uint64_t>();
if (!component) continue;
if (!vtx2Scc[vtx]) continue;
const size_t nFixed = FixUpIndependentRanges::apply(res, &vtx);
const size_t nFixed = FixUpIndependentRanges::apply(res, vtx2Scc, vtx);
if (nFixed) {
nImprovements += nFixed;
ctx.m_breakCyclesContext.m_nImprovements += nFixed;
@ -1340,10 +1355,11 @@ V3DfgPasses::breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) {
} while (nImprovements != prevNImprovements);
if (dumpDfgLevel() >= 9) {
const auto userDataInUse = res.userDataInUse();
V3DfgPasses::colorStronglyConnectedComponents(res);
res.dumpDotFilePrefixed(ctx.prefix() + "breakCycles-remaining",
[](const DfgVertex& vtx) { return vtx.getUser<uint64_t>(); });
Vtx2Scc vtx2Scc = res.makeUserMap<uint64_t>();
V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc);
res.dumpDotFilePrefixed(ctx.prefix() + "breakCycles-remaining", [&](const DfgVertex& vtx) {
return vtx2Scc[vtx]; //
});
}
// If an improvement was made, return the still cyclic improved graph

View File

@ -26,52 +26,51 @@
#include <limits>
#include <vector>
// Similar algorithm used in ExtractCyclicComponents.
// This one sets DfgVertex::user(). See the static 'apply' method below.
class ColorStronglyConnectedComponents final {
static_assert(sizeof(uint32_t[2]) == sizeof(uint64_t), "Incorrect ovverlay size");
// CONSTANTS
static constexpr uint32_t UNASSIGNED = std::numeric_limits<uint32_t>::max();
// TYPES
struct VertexState final {
uint32_t component = UNASSIGNED; // Result component number (0 means not in SCC)
uint32_t index = UNASSIGNED; // Used by Pearce's algorithm for detecting SCCs
VertexState() = default;
VertexState(uint32_t i, uint32_t n)
: component{n}
, index{i} {}
};
// STATE
DfgGraph& m_dfg; // The input graph
const DfgGraph& m_dfg; // The input graph
DfgUserMap<uint64_t>& m_map; // The result map we are computing - also used for traversal
uint32_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph
uint32_t m_index = 0; // Visitation index counter
std::vector<DfgVertex*> m_stack; // The stack used by the algorithm
std::vector<const DfgVertex*> m_stack; // The stack used by the algorithm
// METHODS
void visitColorSCCs(DfgVertex& vtx, VertexState& vtxState) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED, &vtx, "Already visited vertex"););
// Use the bottom 32-bit word as the component number
uint32_t& component(const DfgVertex& vtx) { //
return reinterpret_cast<uint32_t(&)[2]>(m_map[vtx])[0];
}
// Use the top 32-bit word as the visitation index
uint32_t& index(const DfgVertex& vtx) { //
return reinterpret_cast<uint32_t(&)[2]>(m_map[vtx])[1];
}
void visitColorSCCs(const DfgVertex& vtx) {
UDEBUGONLY(UASSERT_OBJ(index(vtx) == UNASSIGNED, &vtx, "Already visited vertex"););
// Visiting vertex
const size_t rootIndex = vtxState.index = ++m_index;
const size_t rootIndex = index(vtx) = ++m_index;
// Visit children
vtx.foreachSink([&](DfgVertex& child) {
VertexState& childSatate = child.user<VertexState>();
vtx.foreachSink([&](const DfgVertex& child) {
// If the child has not yet been visited, then continue traversal
if (childSatate.index == UNASSIGNED) visitColorSCCs(child, childSatate);
if (index(child) == UNASSIGNED) visitColorSCCs(child);
// If the child is not in an SCC
if (childSatate.component == UNASSIGNED) {
if (vtxState.index > childSatate.index) vtxState.index = childSatate.index;
if (component(child) == UNASSIGNED) {
if (index(vtx) > index(child)) index(vtx) = index(child);
}
return false;
});
if (vtxState.index == rootIndex) {
if (index(vtx) == rootIndex) {
// This is the 'root' of an SCC
// A trivial SCC contains only a single vertex
const bool isTrivial = m_stack.empty() //
|| m_stack.back()->getUser<VertexState>().index < rootIndex;
const bool isTrivial = m_stack.empty() || index(*m_stack.back()) < rootIndex;
// We also need a separate component for vertices that drive themselves (which can
// happen for input like 'assign a = a'), as we want to extract them (they are cyclic).
const bool drivesSelf = vtx.foreachSink([&](const DfgVertex& sink) { //
@ -81,17 +80,16 @@ class ColorStronglyConnectedComponents final {
if (!isTrivial || drivesSelf) {
// Allocate new component
++m_nonTrivialSCCs;
vtxState.component = m_nonTrivialSCCs;
component(vtx) = m_nonTrivialSCCs;
while (!m_stack.empty()) {
VertexState& topState = m_stack.back()->getUser<VertexState>();
// Only higher nodes belong to the same SCC
if (topState.index < rootIndex) break;
if (index(*m_stack.back()) < rootIndex) break;
component(*m_stack.back()) = m_nonTrivialSCCs;
m_stack.pop_back();
topState.component = m_nonTrivialSCCs;
}
} else {
// Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph.
vtxState.component = 0;
component(vtx) = 0;
}
} else {
// Not the root of an SCC
@ -102,56 +100,61 @@ class ColorStronglyConnectedComponents final {
void colorSCCs() {
// We know constant nodes have no input edges, so they cannot be part
// of a non-trivial SCC. Mark them as such without any real traversals.
for (DfgConst& vtx : m_dfg.constVertices()) vtx.setUser(VertexState{0, 0});
// Start traversals through variables
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If it has no input or no outputs, it cannot be part of a non-trivial SCC.
if ((!vtx.srcp() && !vtx.defaultp()) || !vtx.hasSinks()) {
UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED || vtxState.component == 0,
&vtx, "Non circular variable must be in a trivial SCC"););
vtxState.index = 0;
vtxState.component = 0;
continue;
}
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
for (const DfgConst& vtx : m_dfg.constVertices()) {
index(vtx) = 0;
component(vtx) = 0;
}
// Start traversals through operations
for (DfgVertex& vtx : m_dfg.opVertices()) {
VertexState& vtxState = vtx.user<VertexState>();
// If not yet visited, start a traversal
if (vtxState.index == UNASSIGNED) visitColorSCCs(vtx, vtxState);
// Initialize state of variable vertices
for (const DfgVertexVar& vtx : m_dfg.varVertices()) {
// If it has no inputs or no outputs, it cannot be part of a non-trivial SCC.
if ((!vtx.srcp() && !vtx.defaultp()) || !vtx.hasSinks()) {
index(vtx) = 0;
component(vtx) = 0;
continue;
}
index(vtx) = UNASSIGNED;
component(vtx) = UNASSIGNED;
}
// Initialize state of operation vertices
for (const DfgVertex& vtx : m_dfg.opVertices()) {
index(vtx) = UNASSIGNED;
component(vtx) = UNASSIGNED;
}
// Start traversals through not yet visited variables
for (const DfgVertexVar& vtx : m_dfg.varVertices()) {
if (index(vtx) == UNASSIGNED) visitColorSCCs(vtx);
}
// Start traversals through not yet visited operations
for (const DfgVertex& vtx : m_dfg.opVertices()) {
if (index(vtx) == UNASSIGNED) visitColorSCCs(vtx);
}
}
explicit ColorStronglyConnectedComponents(DfgGraph& dfg)
: m_dfg{dfg} {
explicit ColorStronglyConnectedComponents(const DfgGraph& dfg, DfgUserMap<uint64_t>& map)
: m_dfg{dfg}
, m_map{map} {
UASSERT(dfg.size() < UNASSIGNED, "Graph too big " << dfg.name());
// Yet another implementation of Pearce's algorithm.
colorSCCs();
// Re-assign user values
m_dfg.forEachVertex([](DfgVertex& vtx) {
const uint64_t component = vtx.getUser<VertexState>().component;
vtx.setUser<uint64_t>(component);
// Re-assign mapped values
m_dfg.forEachVertex([&](const DfgVertex& vtx) {
const uint64_t c = component(vtx);
map[vtx] = c;
});
}
public:
// Sets DfgVertex::user<uint64_t>() for all vertext to:
// - 0, if the vertex is not part of a non-trivial strongly connected component
// and is not part of a self-loop. That is: the Vertex is not part of any cycle.
// - N, if the vertex is part of a non-trivial strongly conneced component or self-loop N.
// That is: each set of vertices that are reachable from each other will have the same
// non-zero value assigned.
// Returns the number of non-trivial SCCs (~distinct cycles)
static uint32_t apply(DfgGraph& dfg) {
return ColorStronglyConnectedComponents{dfg}.m_nonTrivialSCCs;
// See declaration of V3DfgPasses::colorStronglyConnectedComponents
static uint32_t apply(const DfgGraph& dfg, DfgUserMap<uint64_t>& map) {
return ColorStronglyConnectedComponents{dfg, map}.m_nonTrivialSCCs;
}
};
uint32_t V3DfgPasses::colorStronglyConnectedComponents(DfgGraph& dfg) {
return ColorStronglyConnectedComponents::apply(dfg);
uint32_t V3DfgPasses::colorStronglyConnectedComponents(const DfgGraph& dfg,
DfgUserMap<uint64_t>& map) {
return ColorStronglyConnectedComponents::apply(dfg, map);
}

View File

@ -34,6 +34,8 @@ class SplitIntoComponents final {
// STATE
DfgGraph& m_dfg; // The input graph
// Map from vertices to the weekly connected component they belong to
DfgUserMap<size_t> m_component = m_dfg.makeUserMap<size_t>();
const std::string m_prefix; // Component name prefix
std::vector<std::unique_ptr<DfgGraph>> m_components; // The extracted components
// Component counter - starting from 1 as 0 is the default value used as a marker
@ -44,10 +46,10 @@ class SplitIntoComponents final {
std::vector<DfgVertex*> queue;
queue.reserve(m_dfg.size());
// any sort of interesting logic must involve a variable, so we only need to iterate them
// Any sort of interesting logic must involve a variable, so we only need to iterate them
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
// If already assigned this vertex to a component, then continue
if (vtx.user<size_t>()) continue;
if (m_component[vtx]) continue;
// Start depth first traversal at this vertex
queue.push_back(&vtx);
@ -59,10 +61,10 @@ class SplitIntoComponents final {
queue.pop_back();
// Move on if already visited
if (item.user<size_t>()) continue;
if (m_component[item]) continue;
// Assign to current component
item.user<size_t>() = m_componentCounter;
m_component[item] = m_componentCounter;
// Enqueue all sources and sinks of this vertex.
item.foreachSource([&](DfgVertex& src) {
@ -83,7 +85,7 @@ class SplitIntoComponents final {
template <typename Vertex>
void moveVertices(DfgVertex::List<Vertex>& list) {
for (DfgVertex* const vtxp : list.unlinkable()) {
if (const size_t component = vtxp->user<size_t>()) {
if (const size_t component = m_component[vtxp]) {
m_dfg.removeVertex(*vtxp);
m_components[component - 1]->addVertex(*vtxp);
} else {
@ -96,8 +98,6 @@ class SplitIntoComponents final {
SplitIntoComponents(DfgGraph& dfg, const std::string& label)
: m_dfg{dfg}
, m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
// Component number is stored as DfgVertex::user<size_t>()
const auto userDataInUse = m_dfg.userDataInUse();
// Color each component of the graph
colorComponents();
// Allocate the component graphs
@ -126,6 +126,8 @@ std::vector<std::unique_ptr<DfgGraph>> DfgGraph::splitIntoComponents(const std::
class ExtractCyclicComponents final {
// STATE
DfgGraph& m_dfg; // The input graph
// Map from vertices to the component they belong to
DfgUserMap<uint64_t> m_component = m_dfg.makeUserMap<uint64_t>();
const std::string m_prefix; // Component name prefix
const bool m_doExpensiveChecks = v3Global.opt.debugCheck();
// The extracted cyclic components
@ -137,19 +139,21 @@ class ExtractCyclicComponents final {
void addVertexAndExpandSiblings(DfgVertex& vtx, uint64_t component) {
// Do not go past a variable, we will partition the graph there
if (vtx.is<DfgVertexVar>()) return;
// Pick up component value reference
uint64_t& vtxComponentr = m_component.at(vtx);
// Don't need to recurse if the vertex is already in the same component,
// it was either marked through an earlier traversal, in which case it
// was processed recursively, or it will be processed later.
if (vtx.getUser<uint64_t>() == component) return;
if (vtxComponentr == component) return;
// Because all cycles are through a variable, we can't reach another SCC.
UASSERT_OBJ(!vtx.getUser<uint64_t>(), &vtx, "Cycle without variable involvement");
UASSERT_OBJ(!vtxComponentr, &vtx, "Cycle without variable involvement");
// Put this vertex in the component, and continue recursively
vtx.setUser<uint64_t>(component);
vtxComponentr = component;
expandSiblings(vtx, component);
}
void expandSiblings(DfgVertex& vtx, uint64_t component) {
UASSERT_OBJ(vtx.getUser<uint64_t>() == component, &vtx, "Traversal didn't stop");
UASSERT_OBJ(m_component.at(vtx) == component, &vtx, "Traversal didn't stop");
vtx.foreachSink([&](DfgVertex& v) {
addVertexAndExpandSiblings(v, component);
return false;
@ -179,22 +183,22 @@ class ExtractCyclicComponents final {
// component if they are not variables themselves. The assertions below
// must hold because of the assumption above.
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
const uint64_t varComponent = vtx.getUser<uint64_t>();
const uint64_t varComponent = m_component.at(vtx);
if (!varComponent) continue;
if (DfgVertex* const srcp = vtx.srcp()) {
if (!srcp->is<DfgVertexVar>()) {
const uint64_t srcComponent = srcp->getUser<uint64_t>();
uint64_t& srcComponent = m_component.at(srcp);
UASSERT_OBJ(!srcComponent || srcComponent == varComponent, srcp,
"Cycle through 'srcp' that does not go through variable.");
srcp->setUser<uint64_t>(varComponent);
srcComponent = varComponent;
}
}
if (DfgVertex* const defp = vtx.defaultp()) {
if (!defp->is<DfgVertexVar>()) {
const uint64_t defComponent = defp->getUser<uint64_t>();
uint64_t& defComponent = m_component.at(defp);
UASSERT_OBJ(!defComponent || defComponent == varComponent, defp,
"Cycle through 'defaultp' that does not go through variable");
defp->setUser<uint64_t>(varComponent);
defComponent = varComponent;
}
}
}
@ -205,7 +209,7 @@ class ExtractCyclicComponents final {
// We do this by staring a DFS from each vertex that is part of an
// component and add all reachable non-variable vertices to the same.
for (DfgVertex& vtx : m_dfg.opVertices()) {
if (const uint64_t targetComponent = vtx.getUser<uint64_t>()) {
if (const uint64_t targetComponent = m_component.at(vtx)) {
expandSiblings(vtx, targetComponent);
}
}
@ -213,7 +217,7 @@ class ExtractCyclicComponents final {
// Retrieve clone of vertex in the given component
DfgVertexVar* getClone(DfgVertexVar& vtx, uint64_t component) {
UASSERT_OBJ(vtx.getUser<uint64_t>() != component, &vtx, "Vertex is in that component");
UASSERT_OBJ(m_component.at(vtx) != component, &vtx, "Vertex is in that component");
DfgVertexVar*& clonep = m_clones[&vtx][component];
if (!clonep) {
if (DfgVarPacked* const pVtxp = vtx.cast<DfgVarPacked>()) {
@ -230,7 +234,7 @@ class ExtractCyclicComponents final {
}
}
UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type");
clonep->setUser<uint64_t>(component);
m_component[clonep] = component;
clonep->tmpForp(vtx.tmpForp());
}
return clonep;
@ -238,11 +242,11 @@ class ExtractCyclicComponents final {
// Fix edges that cross components
void fixEdges(DfgVertexVar& vtx) {
const uint64_t component = vtx.getUser<uint64_t>();
const uint64_t component = m_component.at(vtx);
// Fix up srcp and dstp (they must be the same component, or variable)
if (DfgVertex* const sp = vtx.srcp()) {
const uint64_t srcComponent = sp->getUser<uint64_t>();
const uint64_t srcComponent = m_component.at(sp);
if (srcComponent != component) {
UASSERT_OBJ(sp->is<DfgVertexVar>(), &vtx, "'srcp' in different component");
getClone(vtx, srcComponent)->srcp(sp);
@ -250,7 +254,7 @@ class ExtractCyclicComponents final {
}
}
if (DfgVertex* const dp = vtx.defaultp()) {
const uint64_t defaultComponent = dp->getUser<uint64_t>();
const uint64_t defaultComponent = m_component.at(dp);
if (defaultComponent != component) {
UASSERT_OBJ(dp->is<DfgVertexVar>(), &vtx, "'defaultp' in different component");
getClone(vtx, defaultComponent)->defaultp(dp);
@ -264,7 +268,7 @@ class ExtractCyclicComponents final {
return false;
});
for (DfgVertex* const sinkp : sinkps) {
const uint64_t sinkComponent = sinkp->getUser<uint64_t>();
const uint64_t sinkComponent = m_component.at(sinkp);
// Same component is OK
if (sinkComponent == component) continue;
DfgVertex* const clonep = getClone(vtx, sinkComponent);
@ -278,7 +282,7 @@ class ExtractCyclicComponents final {
void moveVertices(DfgVertex::List<Vertex>& list) {
for (DfgVertex* const vtxp : list.unlinkable()) {
DfgVertex& vtx = *vtxp;
if (const uint64_t component = vtx.getUser<uint64_t>()) {
if (const uint64_t component = m_component.at(vtx)) {
m_dfg.removeVertex(vtx);
m_components[component - 1]->addVertex(vtx);
}
@ -289,10 +293,10 @@ class ExtractCyclicComponents final {
// Check that edges only cross components at variable boundaries
dfg.forEachVertex([&](DfgVertex& vtx) {
if (vtx.is<DfgVarPacked>()) return;
const uint64_t component = vtx.getUser<uint64_t>();
const uint64_t component = m_component.at(vtx);
vtx.foreachSink([&](DfgVertex& snk) {
if (snk.is<DfgVertexVar>()) return false; // OK to cross at variables
UASSERT_OBJ(component == snk.getUser<uint64_t>(), &vtx,
UASSERT_OBJ(component == m_component.at(snk), &vtx,
"Edge crossing components without variable involvement");
return false;
});
@ -317,10 +321,10 @@ class ExtractCyclicComponents final {
});
}
void extractComponents(uint32_t numNonTrivialSCCs) {
void extractComponents(uint32_t nComponents) {
// Allocate result graphs
m_components.resize(numNonTrivialSCCs);
for (uint32_t i = 0; i < numNonTrivialSCCs; ++i) {
m_components.resize(nComponents);
for (uint32_t i = 0; i < nComponents; ++i) {
m_components[i].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i)});
}
@ -359,16 +363,14 @@ class ExtractCyclicComponents final {
explicit ExtractCyclicComponents(DfgGraph& dfg, const std::string& label)
: m_dfg{dfg}
, m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
// DfgVertex::user<uint64_t> is set to the SCC number by colorStronglyConnectedComponents,
const auto userDataInUse = dfg.userDataInUse();
// Find all the non-trivial SCCs (and trivial cycles) in the graph
const uint32_t numNonTrivialSCCs = V3DfgPasses::colorStronglyConnectedComponents(dfg);
const uint32_t nSCCs = V3DfgPasses::colorStronglyConnectedComponents(dfg, m_component);
// If the graph was acyclic (which should be the common case), then we are done.
if (!numNonTrivialSCCs) return;
if (!nSCCs) return;
// Ensure that component boundaries are always at variables, by expanding SCCs
expandComponents();
// Extract the components
extractComponents(numNonTrivialSCCs);
extractComponents(nSCCs);
}
public:

View File

@ -30,7 +30,7 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
// Remove common sub-expressions
{
// Used by DfgVertex::hash
const auto userDataInUse = dfg.userDataInUse();
DfgUserMap<V3Hash> hashCache = dfg.makeUserMap<V3Hash>();
DfgVertex::EqualsCache equalsCache;
std::unordered_map<V3Hash, std::vector<DfgVertex*>> verticesWithEqualHashes;
@ -38,7 +38,7 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
// Pre-hash variables, these are all unique, so just set their hash to a unique value
uint32_t varHash = 0;
for (DfgVertexVar& vtx : dfg.varVertices()) vtx.user<V3Hash>() = V3Hash{++varHash};
for (const DfgVertexVar& vtx : dfg.varVertices()) hashCache[vtx] = V3Hash{++varHash};
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
// expressions using the same constants to be combined, so we do need to hash equal
@ -49,7 +49,7 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
continue;
}
vtxp->user<V3Hash>() = vtxp->num().toHash() + varHash;
hashCache[vtxp] = vtxp->num().toHash() + varHash;
}
// Combine operation vertices
@ -59,7 +59,7 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
vtxp->unlinkDelete(dfg);
continue;
}
const V3Hash hash = vtxp->hash();
const V3Hash hash = vtxp->hash(hashCache);
std::vector<DfgVertex*>& vec = verticesWithEqualHashes[hash];
bool replaced = false;
for (DfgVertex* const candidatep : vec) {
@ -91,8 +91,8 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) {
}
void V3DfgPasses::removeUnused(DfgGraph& dfg) {
// DfgVertex::user is the next pointer of the work list elements
const auto userDataInUse = dfg.userDataInUse();
// Map from vertex to next vertex in the work list
DfgUserMap<DfgVertex*> nextp = dfg.makeUserMap<DfgVertex*>();
// Head of work list. Note that we want all next pointers in the list to be non-zero (including
// that of the last element). This allows as to do two important things: detect if an element
@ -102,29 +102,22 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
DfgVertex* const sentinelp = reinterpret_cast<DfgVertex*>(&dfg);
DfgVertex* workListp = sentinelp;
// Add all unused operation vertices to the work list. This also allocates all DfgVertex::user.
// Add all unused operation vertices to the work list
for (DfgVertex& vtx : dfg.opVertices()) {
if (vtx.hasSinks()) {
// This vertex is used. Allocate user, but don't add to work list.
vtx.setUser<DfgVertex*>(nullptr);
} else {
// This vertex is unused. Add to work list.
vtx.setUser<DfgVertex*>(workListp);
workListp = &vtx;
}
if (vtx.hasSinks()) continue;
// This vertex is unused. Add to work list.
nextp[vtx] = workListp;
workListp = &vtx;
}
// Also add all unused temporaries created during synthesis
for (DfgVertexVar& vtx : dfg.varVertices()) {
if (!vtx.tmpForp()) continue;
if (vtx.hasSinks() || vtx.hasDfgRefs()) {
// This vertex is used. Allocate user, but don't add to work list.
vtx.setUser<DfgVertex*>(nullptr);
} else {
// This vertex is unused. Add to work list.
vtx.setUser<DfgVertex*>(workListp);
workListp = &vtx;
}
if (vtx.hasSinks()) continue;
if (vtx.hasDfgRefs()) continue;
// This vertex is unused. Add to work list.
nextp[vtx] = workListp;
workListp = &vtx;
}
// Process the work list
@ -132,11 +125,11 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
// Pick up the head
DfgVertex* const vtxp = workListp;
// Detach the head
workListp = vtxp->getUser<DfgVertex*>();
workListp = nextp.at(vtxp);
// Prefetch next item
VL_PREFETCH_RW(workListp);
// This item is now off the work list
vtxp->setUser<DfgVertex*>(nullptr);
nextp.at(vtxp) = nullptr;
// DfgLogic should have been synthesized or removed
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "Should not be DfgLogic");
// If used, then nothing to do, so move on
@ -153,9 +146,9 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
const DfgVertexVar* const varp = src.cast<DfgVertexVar>();
if (varp && !varp->tmpForp()) return false;
// If already in work list then nothing to do
if (src.getUser<DfgVertex*>()) return false;
if (nextp[src]) return false;
// Actually add to work list.
src.setUser<DfgVertex*>(workListp);
nextp[src] = workListp;
workListp = &src;
return false;
});
@ -177,8 +170,6 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
UASSERT(dfg.modulep(), "binToOneHot only works with unscoped DfgGraphs for now");
const auto userDataInUse = dfg.userDataInUse();
// Structure to keep track of comparison details
struct Term final {
DfgVertex* m_vtxp = nullptr; // Vertex to replace
@ -189,13 +180,13 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
, m_inv{inv} {}
};
// Map from 'value beign compared' -> 'terms', stored in DfgVertex::user()
using Val2Terms = std::map<uint32_t, std::vector<Term>>;
// Allocator for Val2Terms, so it's cleaned up on return
std::deque<Val2Terms> val2TermsAllocator;
// List of vertices that are used as sources
std::vector<DfgVertex*> srcps;
// Map from 'vertices' -> 'value beign compared' -> 'terms'
using Val2Terms = std::map<uint32_t, std::vector<Term>>;
DfgUserMap<Val2Terms> vtx2Val2Terms = dfg.makeUserMap<Val2Terms>();
// Only consider input variables from a reasonable range:
// - not too big to avoid huge tables, you are doomed anyway at that point..
// - not too small, as it's probably not worth it
@ -255,16 +246,12 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
// Not a comparison-like vertex
continue;
}
// Grab the Val2Terms entry
Val2Terms*& val2Termspr = srcp->user<Val2Terms*>();
if (!val2Termspr) {
// Remeber and allocate on first encounter
srcps.emplace_back(srcp);
val2TermsAllocator.emplace_back();
val2Termspr = &val2TermsAllocator.back();
}
// Grab Val2Terms for this vertex
Val2Terms& val2Terms = vtx2Val2Terms[srcp];
// Remeber and on first encounter
if (val2Terms.empty()) srcps.emplace_back(srcp);
// Record term
(*val2Termspr)[val].emplace_back(&vtx, inv);
val2Terms[val].emplace_back(&vtx, inv);
++nTerms;
}
@ -281,7 +268,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
// Create decoders for each srcp
for (DfgVertex* const srcp : srcps) {
const Val2Terms& val2Terms = *srcp->getUser<Val2Terms*>();
const Val2Terms& val2Terms = vtx2Val2Terms[srcp];
// If not enough terms in this vertex, ignore
if (val2Terms.size() < TERM_LIMIT) continue;
@ -422,7 +409,8 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
}
void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
const auto userDataInUse = dfg.userDataInUse();
// Map from vertex to next vertex in the work list
DfgUserMap<DfgVertex*> nextp = dfg.makeUserMap<DfgVertex*>();
// Head of work list. Note that we want all next pointers in the list to be non-zero
// (including that of the last element). This allows us to do two important things: detect
@ -434,16 +422,16 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// Add all variables to the initial work list
for (DfgVertexVar& vtx : dfg.varVertices()) {
vtx.setUser<DfgVertex*>(workListp);
nextp[vtx] = workListp;
workListp = &vtx;
}
const auto addToWorkList = [&](DfgVertex& vtx) {
// If already in work list then nothing to do
DfgVertex*& nextInWorklistp = vtx.user<DfgVertex*>();
if (nextInWorklistp) return false;
DfgVertex*& nextpr = nextp[vtx];
if (nextpr) return false;
// Actually add to work list.
nextInWorklistp = workListp;
nextpr = workListp;
workListp = &vtx;
return false;
};
@ -462,9 +450,9 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// Pick up the head of the work list
DfgVertex* const vtxp = workListp;
// Detach the head
workListp = vtxp->getUser<DfgVertex*>();
workListp = nextp.at(vtxp);
// Reset user pointer so it can be added back to the work list later
vtxp->setUser<DfgVertex*>(nullptr);
nextp.at(vtxp) = nullptr;
// Prefetch next item
VL_PREFETCH_RW(workListp);

View File

@ -64,14 +64,14 @@ void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Construct binary to oneHot decoders
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED;
// Sets DfgVertex::user<uint64_t>() for all vertext to:
// Populates the given DfgUserMap for all vertext to:
// - 0, if the vertex is not part of a non-trivial strongly connected component
// and is not part of a self-loop. That is: the Vertex is not part of any cycle.
// - N, if the vertex is part of a non-trivial strongly conneced component or self-loop N.
// That is: each set of vertices that are reachable from each other will have the same
// non-zero value assigned.
// Returns the number of non-trivial SCCs (distinct cycles)
uint32_t colorStronglyConnectedComponents(DfgGraph&) VL_MT_DISABLED;
uint32_t colorStronglyConnectedComponents(const DfgGraph&, DfgUserMap<uint64_t>&) VL_MT_DISABLED;
// Common subexpression elimination
void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED;
// Inline fully driven variables

View File

@ -130,6 +130,8 @@ class V3DfgPeephole final : public DfgVisitor {
// STATE
DfgGraph& m_dfg; // The DfgGraph being visited
// Map from vertex to next vertex on work list
DfgUserMap<DfgVertex*> m_nextp = m_dfg.makeUserMap<DfgVertex*>();
V3DfgPeepholeContext& m_ctx; // The config structure
AstNodeDType* const m_bitDType = V3Dfg::dtypePacked(1); // Common, so grab it up front
// Head of work list. Note that we want all next pointers in the list to be non-zero (including
@ -156,9 +158,9 @@ class V3DfgPeephole final : public DfgVisitor {
// We only process actual operation vertices
if (vtxp->is<DfgConst>() || vtxp->is<DfgVertexVar>()) return;
// If already in work list then nothing to do
if (vtxp->getUser<DfgVertex*>()) return;
if (m_nextp[vtxp]) return;
// Actually add to work list.
vtxp->setUser<DfgVertex*>(m_workListp);
m_nextp[vtxp] = m_workListp;
m_workListp = vtxp;
}
@ -181,7 +183,7 @@ class V3DfgPeephole final : public DfgVisitor {
addSourcesToWorkList(vtxp);
// If in work list then we can't delete it just yet (as we can't remove from the middle of
// the work list), but it will be deleted when the work list is processed.
if (vtxp->getUser<DfgVertex*>()) return;
if (m_nextp[vtxp]) return;
// Otherwise we can delete it now.
// Remove from cache
m_cache.invalidateByValue(vtxp);
@ -230,14 +232,8 @@ class V3DfgPeephole final : public DfgVisitor {
Vertex* make(FileLine* flp, AstNodeDType* dtypep, Operands... operands) {
// Find or create an equivalent vertex
Vertex* const vtxp = m_cache.getOrCreate<Vertex, Operands...>(flp, dtypep, operands...);
// Add to work list.
DfgVertex*& workListNextp = vtxp->template user<DfgVertex*>();
if (!workListNextp) {
workListNextp = m_workListp;
m_workListp = vtxp;
}
// Add to work list
addToWorkList(vtxp);
// Return new node
return vtxp;
}
@ -1755,13 +1751,9 @@ class V3DfgPeephole final : public DfgVisitor {
: m_dfg{dfg}
, m_ctx{ctx} {
// DfgVertex::user is the next pointer of the work list elements
const auto userDataInUse = m_dfg.userDataInUse();
// Add all vertices to the work list, and to the vertex cache.
// This also allocates all DfgVertex::user.
// Add all operation vertices to the work list and cache
for (DfgVertex& vtx : m_dfg.opVertices()) {
vtx.setUser<DfgVertex*>(m_workListp);
m_nextp[vtx] = m_workListp;
m_workListp = &vtx;
m_cache.cache(&vtx);
}
@ -1771,9 +1763,9 @@ class V3DfgPeephole final : public DfgVisitor {
// Pick up the head
DfgVertex* const vtxp = m_workListp;
// Detach the head and prefetch next
m_workListp = vtxp->getUser<DfgVertex*>();
m_workListp = m_nextp[vtxp];
VL_PREFETCH_RW(m_workListp);
vtxp->setUser<DfgVertex*>(nullptr);
m_nextp[vtxp] = nullptr;
// Remove unused vertices as we gp
if (!vtxp->hasSinks()) {
deleteVertex(vtxp);

View File

@ -1822,8 +1822,8 @@ void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) {
// Otherwise figure out which vertices are worth synthesizing.
// Find cycles
const auto userDataInUse = dfg.userDataInUse();
V3DfgPasses::colorStronglyConnectedComponents(dfg);
DfgUserMap<uint64_t> scc = dfg.makeUserMap<uint64_t>();
V3DfgPasses::colorStronglyConnectedComponents(dfg, scc);
// First, gather variables, we will then attempt to synthesize all their drivers
std::vector<DfgVertexVar*> varps;
@ -1832,7 +1832,7 @@ void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) {
if (!var.srcp()) continue;
// Circular variable - synthesize
if (var.getUser<uint64_t>()) {
if (scc.at(var)) {
varps.emplace_back(&var);
continue;
}