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

View File

@ -54,10 +54,18 @@
#define VL_NOT_FINAL // This #define fixes broken code folding in the CLion IDE #define VL_NOT_FINAL // This #define fixes broken code folding in the CLion IDE
#endif #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 DfgEdge;
class DfgVertex; class DfgVertex;
class DfgGraph; class DfgGraph;
class DfgVisitor; 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 // Specialization of std::hash for a std::pair<const DfgVertex*, const DfgVertex*> for use below
template <> template <>
@ -191,8 +199,8 @@ class DfgVertex VL_NOT_FINAL {
friend class DfgGraph; friend class DfgGraph;
friend class DfgEdge; friend class DfgEdge;
friend class DfgVisitor; friend class DfgVisitor;
template <typename, bool>
using UserDataStorage = void*; // Storage allocated for user data friend class DfgUserMap;
// STATE // STATE
V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph
@ -201,10 +209,16 @@ class DfgVertex VL_NOT_FINAL {
FileLine* const m_filelinep; // Source location FileLine* const m_filelinep; // Source location
AstNodeDType* m_dtypep; // Data type of the result of this vertex - mutable for efficiency 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 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 // METHODS
// Visitor accept method // Visitor accept method
@ -252,8 +266,10 @@ public:
FileLine* fileline() const { return m_filelinep; } FileLine* fileline() const { return m_filelinep; }
// The data type of the result of the vertex // The data type of the result of the vertex
AstNodeDType* dtypep() const { return m_dtypep; } 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); } bool isPacked() const { return VN_IS(dtypep(), BasicDType); }
// Is it an array type
bool isArray() const { return !isPacked(); }
// Width of result // Width of result
uint32_t width() const { uint32_t width() const {
UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'"); UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'");
@ -265,19 +281,6 @@ public:
return VN_AS(dtypep(), UnpackArrayDType)->elementsConst(); 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 // Cache type for 'equals' below
using EqualsCache = std::unordered_map<std::pair<const DfgVertex*, const DfgVertex*>, uint8_t>; 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). // Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex).
// Uses user data for caching hashes // Uses the given DfgUserMap for caching hashes
V3Hash hash() VL_MT_DISABLED; V3Hash hash(DfgUserMap<V3Hash>& cache) VL_MT_DISABLED;
// Predicate: has 1 or more sinks // Predicate: has 1 or more sinks
bool hasSinks() const { return !m_sinks.empty(); } bool hasSinks() const { return !m_sinks.empty(); }
@ -465,34 +468,7 @@ public:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Dataflow graph // Dataflow graph
class DfgGraph final { class DfgGraph final {
friend class DfgVertex; friend class DfgUserMapBase;
// 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;
}
};
// MEMBERS // MEMBERS
@ -503,16 +479,18 @@ class DfgGraph final {
DfgVertex::List<DfgVertexVar> m_varVertices; // The variable vertices in the graph DfgVertex::List<DfgVertexVar> m_varVertices; // The variable vertices in the graph
DfgVertex::List<DfgConst> m_constVertices; // The constant 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 DfgVertex::List<DfgVertex> m_opVertices; // The operation vertices in the graph
size_t m_size = 0; // Number of 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), // Parent of the graph (i.e.: the module containing the logic represented by this graph),
// or nullptr when run after V3Scope // or nullptr when run after V3Scope
AstModule* const m_modulep; AstModule* const m_modulep;
const std::string m_name; // Name of graph - need not be unique const std::string m_name; // Name of graph - need not be unique
std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy 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: public:
// CONSTRUCTOR // CONSTRUCTOR
explicit DfgGraph(AstModule* modulep, const string& name = "") VL_MT_DISABLED; explicit DfgGraph(AstModule* modulep, const string& name = "") VL_MT_DISABLED;
@ -528,14 +506,9 @@ public:
// Name of this graph // Name of this graph
const string& name() const { return m_name; } const string& name() const { return m_name; }
// Reset Vertex user data // Create a new DfgUserMap
UserDataInUse userDataInUse() { template <typename T_User>
UASSERT(!m_userCurrent, "Conflicting use of DfgVertex user data"); inline DfgUserMap<T_User> makeUserMap() const;
++m_userCnt;
UASSERT(m_userCnt, "'m_userCnt' overflow");
m_userCurrent = m_userCnt;
return UserDataInUse{this};
}
// Access to vertex lists // Access to vertex lists
DfgVertex::List<DfgVertexVar>& varVertices() { return m_varVertices; } DfgVertex::List<DfgVertexVar>& varVertices() { return m_varVertices; }
@ -547,6 +520,9 @@ public:
// Add DfgVertex to this graph (assumes not yet contained). // Add DfgVertex to this graph (assumes not yet contained).
void addVertex(DfgVertex& vtx) { 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 // Note: changes here need to be replicated in DfgGraph::mergeGraphs
++m_size; ++m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) { if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
@ -556,12 +532,17 @@ public:
} else { } else {
m_opVertices.linkBack(&vtx); m_opVertices.linkBack(&vtx);
} }
vtx.m_userCnt = 0; vtx.m_userGeneration = 0;
vtx.m_graphp = this; #ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
} }
// Remove DfgVertex form this graph (assumes it is contained). // Remove DfgVertex form this graph (assumes it is contained).
void removeVertex(DfgVertex& vtx) { 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 // Note: changes here need to be replicated in DfgGraph::mergeGraphs
--m_size; --m_size;
if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) { if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
@ -571,8 +552,10 @@ public:
} else { } else {
m_opVertices.unlink(&vtx); m_opVertices.unlink(&vtx);
} }
vtx.m_userCnt = 0; vtx.m_userGeneration = 0;
vtx.m_graphp = nullptr; #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 // 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; 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 // Inline method definitions
@ -684,57 +798,11 @@ void DfgEdge::relinkSrcp(DfgVertex* srcp) {
// }}} // }}}
// DfgVertex {{{ // DfgGraph {{{
template <typename T_User> template <typename T_User>
T_User& DfgVertex::user() { DfgUserMap<T_User> DfgGraph::makeUserMap() const {
static_assert(sizeof(T_User) <= sizeof(UserDataStorage), return DfgUserMap<T_User>{this};
"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;
} }
// }}} // }}}

View File

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

View File

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

View File

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

View File

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

View File

@ -64,14 +64,14 @@ void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
// Construct binary to oneHot decoders // Construct binary to oneHot decoders
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED; 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 // - 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. // 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. // - 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 // That is: each set of vertices that are reachable from each other will have the same
// non-zero value assigned. // non-zero value assigned.
// Returns the number of non-trivial SCCs (distinct cycles) // 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 // Common subexpression elimination
void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED; void cse(DfgGraph&, V3DfgCseContext&) VL_MT_DISABLED;
// Inline fully driven variables // Inline fully driven variables

View File

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

View File

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