// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // // This is a data-flow graph based representation of combinational logic, // the main difference from a V3Graph is that DfgVertex owns the storage // of it's input edges (operands/sources/arguments), and can access each // input edge directly by indexing, making modifications more efficient // than the linked list based structures used by V3Graph. // // A bulk of the DfgVertex sub-types are generated by astgen, and are // analogous to the corresponding AstNode sub-types. // // See also the internals documentation docs/internals.rst // //************************************************************************* #ifndef VERILATOR_V3DFG_H_ #define VERILATOR_V3DFG_H_ #include "config_build.h" #include "verilatedos.h" #include "V3Ast.h" #include "V3Cfg.h" #include "V3DfgDataType.h" #include "V3Error.h" #include "V3Global.h" #include "V3Hash.h" #include "V3List.h" #include "V3Dfg__gen_forward_class_decls.h" // From ./astgen #include #include #include #include #include #include #include #ifndef VL_NOT_FINAL #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 inline constexpr bool fitsSpaceAllocatedFor() { return sizeof(T) <= sizeof(U) && alignof(T) <= alignof(U); } class DfgEdge; class DfgVertex; class DfgGraph; class DfgVisitor; template ()> class DfgUserMap; namespace V3Dfg { //----------------------------------------------------------------------- // Functions for compatibility tests // Returns true if variable can be represented in the graph inline bool isSupported(const AstVar* varp) { if (varp->isIfaceRef()) return false; // Cannot handle interface references if (varp->delayp()) return false; // Cannot handle delayed variables if (varp->isSc()) return false; // SystemC variables are special and rare, we can ignore if (varp->dfgMultidriven()) return false; // Discovered as multidriven on earlier DFG run return DfgDataType::fromAst(varp->dtypep()); } // Returns true if variable can be represented in the graph inline bool isSupported(const AstVarScope* vscp) { const AstNodeModule* const modp = vscp->scopep()->modp(); if (VN_IS(modp, Module)) { // Regular module supported } else if (const AstIface* const ifacep = VN_CAST(modp, Iface)) { // Interfaces supported if there are no virtual interfaces for // them, otherwise they cannot be resovled statically. if (ifacep->hasVirtualRef()) return false; } else { return false; // Anything else (package, class, etc) not supported } // Check the AstVar return isSupported(vscp->varp()); } } //namespace V3Dfg //------------------------------------------------------------------------------ // Dataflow graph vertex type enum class VDfgType final { public: #include "V3Dfg__gen_type_enum.h" // From ./astgen const en m_e; VDfgType() = delete; // VDfgType is interconvetible with VDfgType::en // cppcheck-suppress noExplicitConstructor constexpr VDfgType(en _e) : m_e{_e} {} constexpr operator en() const { return m_e; } }; constexpr bool operator==(VDfgType lhs, VDfgType rhs) { return lhs.m_e == rhs.m_e; } constexpr bool operator==(VDfgType lhs, VDfgType::en rhs) { return lhs.m_e == rhs; } constexpr bool operator==(VDfgType::en lhs, VDfgType rhs) { return lhs == rhs.m_e; } inline std::ostream& operator<<(std::ostream& os, const VDfgType& t) { return os << t.ascii(); } //------------------------------------------------------------------------------ // Dataflow graph edge class DfgEdge final { friend class DfgVertex; DfgVertex* m_srcp = nullptr; // The source vertex driving this edge - might be unconnected DfgVertex* const m_dstp; // The vertex driven by this edge, which owns this edge, so immutable V3ListLinks m_links; // V3List links in the list of sinks of m_srcp DfgEdge() = delete; VL_UNCOPYABLE(DfgEdge); VL_UNMOVABLE(DfgEdge); V3ListLinks& links() { return m_links; } using List = V3List; public: explicit DfgEdge(DfgVertex* dstp) : m_dstp{dstp} {} ~DfgEdge() { unlinkSrcp(); } // The source (driver) of this edge DfgVertex* srcp() const { return m_srcp; } // The sink (consumer) of this edge DfgVertex* dstp() const { return m_dstp; } // Remove driver of this edge inline void unlinkSrcp(); // Relink this edge to be driven from the given new source vertex inline void relinkSrcp(DfgVertex* srcp); }; //------------------------------------------------------------------------------ // Dataflow graph vertex class DfgVertex VL_NOT_FINAL { friend class DfgGraph; friend class DfgEdge; friend class DfgVisitor; template friend class DfgUserMap; // STATE V3ListLinks m_links; // V3List links in the DfgGraph std::vector> m_inputps; // Input edges, as vector, for fast indexing DfgEdge::List m_sinks; // List of sink edges of this vertex FileLine* const m_filelinep; // Source location const DfgDataType& m_dtype; // Data type of the result of this vertex const VDfgType m_type; // Vertex type tag // 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 virtual void accept(DfgVisitor& v) = 0; // Acessor for type List V3ListLinks& links() { return m_links; } public: // List type that can store Vertex (which must be a DfgVertex) instances via m_links template using List = V3List; protected: // CONSTRUCTOR DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt) VL_MT_DISABLED; // Use unlinkDelete instead virtual ~DfgVertex() VL_MT_DISABLED = default; // Create a new input edge and return it DfgEdge* newInput() { m_inputps.emplace_back(new DfgEdge{this}); return m_inputps.back().get(); } // Unlink all inputs and reset to no inputs void resetInputs() { m_inputps.clear(); } public: // Get input 'i' DfgVertex* inputp(size_t i) const { return m_inputps[i]->srcp(); } // Relink input 'i' void inputp(size_t i, DfgVertex* vtxp) { m_inputps[i]->relinkSrcp(vtxp); } // The number of inputs this vertex has. Some might be unconnected. size_t nInputs() const { return m_inputps.size(); } // The type of this vertex VDfgType type() const { return m_type; } // Source location FileLine* fileline() const { return m_filelinep; } // The data type of the result of the vertex const DfgDataType& dtype() const { return m_dtype; } // Shorthands for accessors of 'dtype()' bool isPacked() const { return m_dtype.isPacked(); } bool isArray() const { return m_dtype.isArray(); } uint32_t size() const { return m_dtype.size(); } // Type check + size uint32_t width() const { UASSERT_OBJ(m_dtype.isPacked(), this, "Non packed vertex has no 'width'"); return m_dtype.size(); } // Predicate: has 1 or more sinks bool hasSinks() const { return !m_sinks.empty(); } // Predicate: has 2 or more sinks bool hasMultipleSinks() const { return m_sinks.hasMultipleElements(); } // Fanout (number of sinks) of this vertex (expensive to compute) uint32_t fanout() const VL_MT_DISABLED; // Return a canonical variable vertex that holds the value of this vertex, // or nullptr if no such variable exists in the graph. This is O(fanout). DfgVertexVar* getResultVar() VL_MT_DISABLED; // Cache type for 'scopep' below using ScopeCache = std::unordered_map; // Retrieve the prefred AstScope this vertex belongs to. For variable // vertices this is defined. For operation vertices, we try to find a // scope based on variables in the upstream logic cone (inputs). If // there isn't one, (beceuse the whole upstream cone is constant...), // then the root scope is returned. If 'tryResultVar' is true, we will // condier the scope of 'getResultVar' first, if it exists. // Only call this with a scoped DfgGraph AstScope* scopep(ScopeCache& cache, bool tryResultVar = false) VL_MT_DISABLED; // If the node has a single sink, return it, otherwise return nullptr DfgVertex* singleSink() const { return m_sinks.hasSingleElement() ? m_sinks.frontp()->dstp() : nullptr; } // First sink of the vertex, if any, otherwise nullptr DfgVertex* firtsSinkp() { return m_sinks.empty() ? nullptr : m_sinks.frontp()->dstp(); } // Unlink from container (graph or builder), then delete this vertex void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED; // Relink all sinks to be driven from the given new source void replaceWith(DfgVertex* vtxp) { UASSERT_OBJ(vtxp != this, this, "Replacing DfgVertex with itself"); UASSERT_OBJ(vtxp->dtype() == dtype(), this, "Replacement DfgVertex has different type"); while (!m_sinks.empty()) m_sinks.frontp()->relinkSrcp(vtxp); } // Calls given function 'f' for each source vertex of this vertex. If 'f' // returns true, further sources are not iterated and this method returns // true itself. Unconnected source edges are not iterated. bool foreachSource(std::function f) { for (const std::unique_ptr& edgep : m_inputps) { if (DfgVertex* const srcp = edgep->srcp()) { if (f(*srcp)) return true; } } return false; } // Calls given function 'f' for each source vertex of this vertex. If 'f' // returns true, further sources are not iterated and this method returns // true itself. Unconnected source edges are not iterated. bool foreachSource(std::function f) const { for (const std::unique_ptr& edgep : m_inputps) { if (DfgVertex* const srcp = edgep->srcp()) { if (f(*srcp)) return true; } } return false; } // Calls given function 'f' for each sink vertex of this vertex. If 'f' // returns true, further sinks are not iterated and this method returns // true itself. Unlinking/deleting the given sink during iteration is safe, // but not other sinks of this vertex. bool foreachSink(std::function f) { for (const DfgEdge* const edgep : m_sinks.unlinkable()) { if (f(*edgep->dstp())) return true; } return false; } // Calls given function 'f' for each sink vertex of this vertex. If 'f' // returns true, further sinks are not iterated and this method returns // true itself. bool foreachSink(std::function f) const { for (const DfgEdge& edge : m_sinks) { if (f(*edge.dstp())) return true; } return false; } // Is this vertex cheaper to re-compute than to load out of memoy inline bool isCheaperThanLoad() const; // Methods that allow DfgVertex to participate in error reporting/messaging // LCOV_EXCL_START void v3errorEnd(std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex) { m_filelinep->v3errorEnd(str); } void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN VL_RELEASE(V3Error::s().m_mutex) { m_filelinep->v3errorEndFatal(str); } string warnContextPrimary() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnContextPrimary(); } string warnContextSecondary() const { return fileline()->warnContextSecondary(); } string warnMore() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnMore(); } string warnOther() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnOther(); } // LCOV_EXCL_STOP private: // For internal use only. // Note: specializations for particular vertex types are provided by 'astgen' template inline static bool privateTypeTest(const DfgVertex* nodep); public: // Subtype test template bool is() const { static_assert(std::is_base_of::value, "'T' must be a subtype of DfgVertex"); return privateTypeTest::type>(this); } // Ensure subtype, then cast to that type template T* as() { UASSERT_OBJ(is(), this, "DfgVertex is not of expected type, but instead has type '" << typeName() << "'"); return static_cast(this); } template const T* as() const { UASSERT_OBJ(is(), this, "DfgVertex is not of expected type, but instead has type '" << typeName() << "'"); return static_cast(this); } // Cast to subtype, or null if different template T* cast() { return is() ? static_cast(this) : nullptr; } template const T* cast() const { return is() ? static_cast(this) : nullptr; } // Human-readable vertex type as string for debugging std::string typeName() const { return m_type.ascii(); } // Human-readable name for source operand with given index for debugging virtual std::string srcName(size_t idx) const = 0; }; // DfgVertex visitor class DfgVisitor VL_NOT_FINAL { public: // Dispatch to most specific 'visit' method on 'vtxp' void iterate(DfgVertex* vtxp) { vtxp->accept(*this); } // Least specific visit method is abstract virtual void visit(DfgVertex* nodep) = 0; #include "V3Dfg__gen_visitor_decls.h" // From ./astgen }; // DfgVertex subclasses #include "V3DfgVertices.h" // Specializations of privateTypeTest #include "V3Dfg__gen_type_tests.h" // From ./astgen //------------------------------------------------------------------------------ // Dataflow graph class DfgGraph final { friend class DfgUserMapBase; // MEMBERS // Variables and constants make up a significant proportion of vertices (40-50% was observed // in large designs), and they can often be treated specially in algorithms, which in turn // enables significant Verilation performance gains, so we keep these in separate lists for // direct access. DfgVertex::List m_varVertices; // The variable vertices in the graph DfgVertex::List m_constVertices; // The constant vertices in the graph DfgVertex::List m_opVertices; // The operation vertices in the graph size_t m_size = 0; // Number of vertices in the graph // 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; ~DfgGraph() VL_MT_DISABLED; VL_UNCOPYABLE(DfgGraph); // METHODS public: // Number of vertices in this graph size_t size() const { return m_size; } // Parent module - or nullptr when run after V3Scope AstModule* modulep() const { return m_modulep; } // Name of this graph const string& name() const { return m_name; } // Create a new DfgUserMap template inline DfgUserMap makeUserMap() const; // Access to vertex lists DfgVertex::List& varVertices() { return m_varVertices; } const DfgVertex::List& varVertices() const { return m_varVertices; } DfgVertex::List& constVertices() { return m_constVertices; } const DfgVertex::List& constVertices() const { return m_constVertices; } DfgVertex::List& opVertices() { return m_opVertices; } const DfgVertex::List& opVertices() const { return m_opVertices; } // 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()) { m_constVertices.linkBack(cVtxp); } else if (DfgVertexVar* const vVtxp = vtx.cast()) { m_varVertices.linkBack(vVtxp); } else { m_opVertices.linkBack(&vtx); } 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()) { m_constVertices.unlink(cVtxp); } else if (DfgVertexVar* const vVtxp = vtx.cast()) { m_varVertices.unlink(vVtxp); } else { m_opVertices.unlink(&vtx); } 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 // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however // not safe to delete/unlink any vertex in the same graph other than the one passed to 'f'. void forEachVertex(std::function f) { for (DfgVertexVar* const vtxp : m_varVertices.unlinkable()) f(*vtxp); for (DfgConst* const vtxp : m_constVertices.unlinkable()) f(*vtxp); for (DfgVertex* const vtxp : m_opVertices.unlinkable()) f(*vtxp); } // 'const' variant of 'forEachVertex'. No mutation allowed. void forEachVertex(std::function f) const { for (const DfgVertexVar& vtx : m_varVertices) f(vtx); for (const DfgConst& vtx : m_constVertices) f(vtx); for (const DfgVertex& vtx : m_opVertices) f(vtx); } // Return an identical, independent copy of this graph. Vertex and edge order might differ. std::unique_ptr clone() const VL_MT_DISABLED; // Merge contents of other graphs into this graph. Deletes the other graphs. // DfgVertexVar instances representing the same Ast variable are unified. void mergeGraphs(std::vector>&& otherps) VL_MT_DISABLED; // Genarete a unique name. The provided 'prefix' and 'n' values will be part of the name, and // must be unique (as a pair) in each invocation for this graph. std::string makeUniqueName(const std::string& prefix, size_t n) VL_MT_DISABLED; // Create a new variable with the given name and data type. For a Scoped // Dfg, the AstScope where the corresponding AstVarScope will be inserted // must be provided DfgVertexVar* makeNewVar(FileLine*, const std::string& name, const DfgDataType&, AstScope*) VL_MT_DISABLED; // Split this graph into individual components (unique sub-graphs with no edges between them). // Also removes any vertices that are not weakly connected to any variable. // Leaves 'this' graph empty. std::vector> splitIntoComponents(const std::string& label) VL_MT_DISABLED; // Extract cyclic sub-graphs from 'this' graph. Cyclic sub-graphs are those that contain at // least one strongly connected component (SCC) plus any other vertices that feed or sink from // the SCCs, up to a variable boundary. This means that the returned graphs are guaranteed to // be cyclic, but they are not guaranteed to be strongly connected (however, they are always // at least weakly connected). Trivial SCCs that are acyclic (i.e.: vertices that are not part // of a cycle) are left in 'this' graph. This means that at the end 'this' graph is guaranteed // to be a DAG (acyclic). 'this' will not necessarily be a connected graph at the end, even if // it was originally connected. std::vector> extractCyclicComponents(const std::string& label) VL_MT_DISABLED; //----------------------------------------------------------------------- // Debug dumping // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of // the graph which is included in the output. // If the predicate function 'p' is provided, only those vertices are dumped that satifty it. void dumpDot(std::ostream& os, const std::string& label, std::function p = {}) const VL_MT_DISABLED; // Dump graph in Graphviz format into a new file with the given 'filename'. 'label' is added to // the name of the graph which is included in the output. // If the predicate function 'p' is provided, only those vertices are dumped that satifty it. void dumpDotFile(const std::string& filename, const std::string& label, std::function p = {}) const VL_MT_DISABLED; // Same as dumpDotFile, but returns the contents as a string. std::string dumpDotString(const std::string& label, std::function p = {}) const VL_MT_DISABLED; // Dump graph in Graphviz format into a new automatically numbered debug file. 'label' is // added to the name of the graph, which is included in the file name and the output. // If the predicate function 'p' is provided, only those vertices are dumped that satifty it. void dumpDotFilePrefixed(const std::string& label, std::function p = {}) const VL_MT_DISABLED; // Returns the set of vertices in the upstream cones of the given vertices std::unique_ptr> sourceCone(const std::vector&) const VL_MT_DISABLED; // Returns the set of vertices in the downstream cones of the given vertices std::unique_ptr> sinkCone(const std::vector&) 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 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 class DfgUserMap final : public DfgUserMapBase { static_assert(fitsSpaceAllocatedFor(), "'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(&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(&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 class DfgUserMap final : public DfgUserMapBase { static_assert(fitsSpaceAllocatedFor(), "'T_Value*' does not fit 'DfgVertex::m_userStorage'"); friend class DfgGraph; // STATE std::deque 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(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(vtx.m_userStorage); } // Same as above with pointer as key T_Value& at(const DfgVertex* vtxp) const { return (*this).at(*vtxp); } }; //------------------------------------------------------------------------------ // Worklist for processing DfgVertices, implemented via DfgUserMap class DfgWorklist final { // STATE // The Graph being processed DfgGraph& m_dfg; // Map from vertex to next vertex in the work list DfgUserMap m_nextp = m_dfg.makeUserMap(); // We want all 'nextp' pointers for vertices that are in the worklist to be // non-zero (including that of the last element). This allows us to do two // important things: detect if an element is in the list by checking for a // non-zero 'nextp'', and easy prefetching without conditionals. The // address of the worklist itself is a good sentinel as it is a valid // memory address, and we can easily check for the end of the list. DfgVertex* const m_sentinelp = reinterpret_cast(this); // Head of work list DfgVertex* m_headp = m_sentinelp; public: // CONSTRUCTOR explicit DfgWorklist(DfgGraph& dfg) : m_dfg{dfg} {} VL_UNCOPYABLE(DfgWorklist); VL_UNMOVABLE(DfgWorklist); ~DfgWorklist() = default; // METHODS // If 'vtx' is not in the worklist already, add it at the head of the list // and return ture. If 'vtx' is already in the work list, then do nothing // and return false. bool push_front(DfgVertex& vtx) { // Pick up reference to the next pointer DfgVertex*& nextpr = m_nextp[vtx]; // If already in work list then nothing to do if (nextpr) return false; // Prepend to work list nextpr = m_headp; m_headp = &vtx; return true; } // Returns ture iff 'vtx' is in the worklist bool contains(const DfgVertex& vtx) { return m_nextp[vtx]; } // Process the worklist by removing the first element, calling on it the // given callable 'f', and repeat until the worklist is empty. The callable // 'f' can add furthere vertices to the worklist. template void foreach(T_Callable&& f) { static_assert(vlstd::is_invocable_r::value, "T_Callable 'f' must have a signature compatible with 'void(DfgVertex&)'"); // Process the work list while (m_headp != m_sentinelp) { // Pick up the head DfgVertex& vtx = *m_headp; // Detach the head m_headp = m_nextp.at(vtx); // Prefetch next item VL_PREFETCH_RW(m_headp); // This item is now off the work list m_nextp.at(vtx) = nullptr; // Apply 'f' f(vtx); } } }; //------------------------------------------------------------------------------ // Inline method definitions // DfgEdge {{{ void DfgEdge::unlinkSrcp() { if (!m_srcp) return; #ifdef VL_DEBUG bool contained = false; for (const DfgEdge& edge : m_srcp->m_sinks) { if (&edge != this) continue; contained = true; break; } UASSERT_OBJ(contained, m_srcp, "'m_srcp' does not have this as sink"); #endif m_srcp->m_sinks.unlink(this); m_srcp = nullptr; } void DfgEdge::relinkSrcp(DfgVertex* srcp) { // Unlink current source, if any unlinkSrcp(); m_srcp = srcp; if (m_srcp) m_srcp->m_sinks.linkFront(this); } // }}} // DfgVertex {{{ bool DfgVertex::isCheaperThanLoad() const { // Array sels are just address computation if (is()) return true; // Small constant select from variable if (const DfgSel* const selp = cast()) { if (!selp->fromp()->is()) return false; if (selp->fromp()->width() <= VL_QUADSIZE) return true; const uint32_t lsb = selp->lsb(); const uint32_t msb = lsb + selp->width() - 1; return VL_BITWORD_E(msb) == VL_BITWORD_E(lsb); } // Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _) if (const DfgConcat* const catp = cast()) { if (catp->width() > VL_QUADSIZE) return false; const DfgConst* const lCatp = catp->lhsp()->cast(); if (!lCatp) return false; if (!lCatp->isZero()) return false; return catp->rhsp()->isCheaperThanLoad(); } // Otherwise probably not return false; } // }}} // DfgGraph {{{ template DfgUserMap DfgGraph::makeUserMap() const { return DfgUserMap{this}; } // }}} #endif