Files
verilator/src/V3Dfg.h
T
Geza Lore ccb4ab9fe3 Optimize Dfg vertex input edge storage (#8313)
Store input edges of fixed arity vertices inline in the vertex class.
This reduces heap allocations, memory fragmentation, and pointer chasing
and speeds up Dfg passes. The extra branch introduced in inputEdgep() is
well predictable and profiling shows branchless alternatives are a loss.
2026-09-18 18:19:53 +01:00

930 lines
37 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// 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 <algorithm>
#include <array>
#include <functional>
#include <new>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#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 <typename T, typename U>
inline constexpr bool fitsSpaceAllocatedFor() {
return sizeof(T) <= sizeof(U) && alignof(T) <= alignof(U);
}
class DfgEdge;
class DfgVertex;
class DfgGraph;
class DfgVisitor;
template <typename T_User, bool = fitsSpaceAllocatedFor<T_User, void*>()>
class DfgUserMap;
//------------------------------------------------------------------------------
// 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<DfgEdge> m_links; // V3List links in the list of sinks of m_srcp
VL_UNCOPYABLE(DfgEdge);
VL_UNMOVABLE(DfgEdge);
V3ListLinks<DfgEdge>& links() { return m_links; }
using List = V3List<DfgEdge, &DfgEdge::links>;
public:
explicit DfgEdge(DfgVertex* dstp)
: m_dstp{dstp} {}
~DfgEdge() { unlinkSrcp(); }
DfgEdge() = delete;
// 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);
};
//------------------------------------------------------------------------------
// Input edge storage of a fixed arity vertices, embedded in the vertex itself.
template <uint32_t N_Edges>
class DfgInlineEdgeStorage final {
static_assert(N_Edges > 0, "'DfgInlineEdgeStorage' must hold at least one edge");
VL_UNCOPYABLE(DfgInlineEdgeStorage);
VL_UNMOVABLE(DfgInlineEdgeStorage);
public:
union { // Union, for manual memory management, so DfgEdge need no implicit constructor
DfgEdge m_edges[N_Edges]; // The input edges of the owning vertex
};
explicit DfgInlineEdgeStorage(DfgVertex* vtxp) {
for (uint32_t i = 0; i < N_Edges; ++i) new (&m_edges[i]) DfgEdge{vtxp};
}
~DfgInlineEdgeStorage() {
for (uint32_t i = 0; i < N_Edges; ++i) m_edges[i].~DfgEdge();
}
DfgInlineEdgeStorage() = delete;
};
//------------------------------------------------------------------------------
// Dataflow graph vertex
class DfgVertex VL_NOT_FINAL {
friend class DfgGraph;
friend class DfgEdge;
friend class DfgVisitor;
template <typename, bool>
friend class DfgUserMap;
friend class DfgVertexVariadic;
// STATE
V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph
DfgEdge* const m_inlineInputsp; // Input edges stored inline in the vertex (iff fixed arity)
uint32_t m_nInputs; // Number of input edges
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<DfgVertex>& links() { return m_links; }
public:
// List type that can store Vertex (which must be a DfgVertex) instances via m_links
template <typename Vertex>
using List = V3List<DfgVertex, &DfgVertex::links, Vertex>;
protected:
// CONSTRUCTOR
DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt,
DfgEdge* inlineInputsp, uint32_t nInputs) VL_MT_DISABLED;
// Use unlinkDelete instead
virtual ~DfgVertex() VL_MT_DISABLED = default;
private:
// Get input edge 'i'
inline DfgEdge* inputEdgep(size_t i) const;
public:
// Get input 'i'
DfgVertex* inputp(size_t i) const { return inputEdgep(i)->srcp(); }
// Relink input 'i'
void inputp(size_t i, DfgVertex* vtxp) { inputEdgep(i)->relinkSrcp(vtxp); }
// The number of inputs this vertex has. Some might be unconnected.
size_t nInputs() const { return m_nInputs; }
// 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();
}
// Has terminating side-effect
bool unsafe() const;
// Type check vertex (for debugging)
void typeCheck(const DfgGraph& dfg) const;
// 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<const DfgVertex*, AstScope*>;
// 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.
template <typename T_Callable>
bool foreachSource(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
for (size_t i = 0; i < m_nInputs; ++i) {
if (DfgVertex* const srcp = inputEdgep(i)->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.
template <typename T_Callable>
bool foreachSource(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
for (size_t i = 0; i < m_nInputs; ++i) {
if (const DfgVertex* const srcp = inputEdgep(i)->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.
template <typename T_Callable>
bool foreachSink(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<bool, T_Callable, DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(DfgVertex&)'");
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.
template <typename T_Callable>
bool foreachSink(T_Callable&& f) const {
static_assert(
vlstd::is_invocable_r<bool, T_Callable, const DfgVertex&>::value,
"T_Callable 'f' must have a signature compatible with 'bool(const DfgVertex&)'");
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 <typename T>
inline static bool privateTypeTest(const DfgVertex* nodep);
public:
// Subtype test
template <typename T>
bool is() const {
static_assert(std::is_base_of<DfgVertex, T>::value, "'T' must be a subtype of DfgVertex");
return privateTypeTest<typename std::remove_cv<T>::type>(this);
}
// Ensure subtype, then cast to that type
template <typename T>
T* as() {
UASSERT_OBJ(is<T>(), this,
"DfgVertex is not of expected type, but instead has type '" << typeName()
<< "'");
return static_cast<T*>(this);
}
template <typename T>
const T* as() const {
UASSERT_OBJ(is<T>(), this,
"DfgVertex is not of expected type, but instead has type '" << typeName()
<< "'");
return static_cast<const T*>(this);
}
// Cast to subtype, or null if different
template <typename T>
T* cast() {
return is<T>() ? static_cast<T*>(this) : nullptr;
}
template <typename T>
const T* cast() const {
return is<T>() ? static_cast<const T*>(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;
// S-expression inspired dump of vertex and operands for debugging
std::string patternString(uint32_t depth = 0) const;
};
// 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<DfgVertexVar> m_varVertices; // The variable vertices in the graph
DfgVertex::List<DfgVertexAst> m_astVertices; // The ast reference vertices in the graph
DfgVertex::List<DfgConst> m_constVertices; // The constant vertices in the graph
DfgVertex::List<DfgVertex> m_opVertices; // The operation vertices in the graph
size_t m_size = 0; // Number of vertices in the graph
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(const string& name = "") VL_MT_DISABLED;
~DfgGraph() VL_MT_DISABLED;
VL_UNCOPYABLE(DfgGraph);
// METHODS
// Number of vertices in this graph
size_t size() const { return m_size; }
// Name of this graph
const string& name() const { return m_name; }
// Create a new DfgUserMap
template <typename T_User>
inline DfgUserMap<T_User> makeUserMap() const;
// Access to vertex lists
DfgVertex::List<DfgVertexVar>& varVertices() { return m_varVertices; }
const DfgVertex::List<DfgVertexVar>& varVertices() const { return m_varVertices; }
DfgVertex::List<DfgVertexAst>& astVertices() { return m_astVertices; }
const DfgVertex::List<DfgVertexAst>& astVertices() const { return m_astVertices; }
DfgVertex::List<DfgConst>& constVertices() { return m_constVertices; }
const DfgVertex::List<DfgConst>& constVertices() const { return m_constVertices; }
DfgVertex::List<DfgVertex>& opVertices() { return m_opVertices; }
const DfgVertex::List<DfgVertex>& 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 (DfgVertexVar* const vVtxp = vtx.cast<DfgVertexVar>()) {
m_varVertices.linkBack(vVtxp);
} else if (DfgVertexAst* const aVtxp = vtx.cast<DfgVertexAst>()) {
m_astVertices.linkBack(aVtxp);
} else if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
m_constVertices.linkBack(cVtxp);
} 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 (DfgVertexVar* const vVtxp = vtx.cast<DfgVertexVar>()) {
m_varVertices.unlink(vVtxp);
} else if (DfgVertexAst* const aVtxp = vtx.cast<DfgVertexAst>()) {
m_astVertices.unlink(aVtxp);
} else if (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
m_constVertices.unlink(cVtxp);
} 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<void(DfgVertex&)> f) {
for (DfgVertexVar* const vtxp : m_varVertices.unlinkable()) f(*vtxp);
for (DfgVertexAst* const vtxp : m_astVertices.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<void(const DfgVertex&)> f) const {
for (const DfgVertexVar& vtx : m_varVertices) f(vtx);
for (const DfgVertexAst& vtx : m_astVertices) f(vtx);
for (const DfgConst& vtx : m_constVertices) f(vtx);
for (const DfgVertex& vtx : m_opVertices) f(vtx);
}
// 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<std::unique_ptr<DfgGraph>>&& 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<std::unique_ptr<DfgGraph>>
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<std::unique_ptr<DfgGraph>>
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<bool(const DfgVertex&)> 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<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Same as dumpDotFile, but returns the contents as a string.
std::string dumpDotString(const std::string& label,
std::function<bool(const DfgVertex&)> 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<bool(const DfgVertex&)> p = {}) const VL_MT_DISABLED;
// Returns the set of vertices in the upstream cones of the given vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>>
sourceCone(const std::vector<const DfgVertex*>&) const VL_MT_DISABLED;
// Returns the set of vertices in the downstream cones of the given vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>>
sinkCone(const std::vector<const DfgVertex*>&) const VL_MT_DISABLED;
// Returns the set of vertices within an 'n' hop neighborhood of the given vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>>
neighborhood(const std::vector<const DfgVertex*>&, size_t n) const VL_MT_DISABLED;
};
namespace V3Dfg {
//-----------------------------------------------------------------------
// Functions for compatibility tests
// 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
}
if (DfgVertexVar::hasRWRefs(vscp)) return false; // Referenced via READWRITE references
// Check the AstVar
AstVar* const varp = vscp->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
return DfgDataType::fromAst(varp->dtypep());
}
} //namespace V3Dfg
//------------------------------------------------------------------------------
// 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 generation 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;
}
public:
~DfgUserMapBase() {
if (m_dfgp) m_dfgp->m_vertexUserInUse = false;
}
DfgUserMapBase& operator=(DfgUserMapBase&&) = delete;
};
// 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);
public:
DfgUserMap(DfgUserMap&&) = default;
~DfgUserMap() = default;
DfgUserMap& operator=(DfgUserMap&&) = delete;
// 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);
public:
DfgUserMap(DfgUserMap&&) = default;
~DfgUserMap() = default;
DfgUserMap& operator=(DfgUserMap&&) = delete;
// 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); }
};
//------------------------------------------------------------------------------
// 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<DfgVertex*> m_nextp = m_dfg.makeUserMap<DfgVertex*>();
// 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<DfgVertex*>(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 <typename T_Callable>
void foreach(T_Callable&& f) {
static_assert(vlstd::is_invocable_r<void, T_Callable, DfgVertex&>::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 {{{
DfgEdge* DfgVertex::inputEdgep(size_t i) const {
UDEBUGONLY(UASSERT_OBJ(i < m_nInputs, this, "Input index out of range"););
if (VL_LIKELY(m_inlineInputsp)) return m_inlineInputsp + i;
// 'm_inlineInputsp' is null exactly for a DfgVertexVariadic
UDEBUGONLY(UASSERT_OBJ(is<DfgVertexVariadic>(), this, "Vertex without input edge storage"););
return static_cast<const DfgVertexVariadic*>(this)->m_edgeps[i].get();
}
bool DfgVertex::isCheaperThanLoad() const {
// Constants
if (is<DfgConst>()) return true;
// Variables
if (is<DfgVertexVar>()) return true;
if (is<DfgPrev>()) return true;
// Array sels are just address computation, but the address itself can be expensive
if (const DfgArraySel* aselp = cast<DfgArraySel>()) {
if (aselp->bitp()->is<DfgMatchMasked>()) return false;
return true;
}
// Small select from variable
if (const DfgSel* const selp = cast<DfgSel>()) {
if (!selp->fromp()->is<DfgVarPacked>()) 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);
}
// Replication of a single cheap bit. Each word of the result is the same
// mask computed by negating that bit, so recomputing it at each use costs
// no more than the load it replaces.
if (const DfgRep* const repp = cast<DfgRep>()) {
const DfgVertex* const srcp = repp->srcp();
return srcp->width() == 1 && srcp->isCheaperThanLoad();
}
// Zero extend of a cheap vertex - Extend(_) was converted to Concat(0, _)
if (const DfgConcat* const catp = cast<DfgConcat>()) {
if (catp->width() > VL_QUADSIZE) return false;
const DfgConst* const lConstp = catp->lhsp()->cast<DfgConst>();
if (!lConstp || !lConstp->isZero()) return false;
return catp->rhsp()->isCheaperThanLoad();
}
// Reduction of a narrow cheap vertex
if (is<DfgRedOr>() //
|| is<DfgRedAnd>() //
|| is<DfgRedXor>()) {
const DfgVertex* const srcp = as<DfgVertexUnary>()->srcp();
return srcp->width() <= VL_QUADSIZE && srcp->isCheaperThanLoad();
}
// Comparisons of a narrow cheap vertex with constant
if (is<DfgEq>() //
|| is<DfgNeq>() //
|| is<DfgLt>() //
|| is<DfgLte>() //
|| is<DfgGt>() //
|| is<DfgGte>() //
|| is<DfgLtS>() //
|| is<DfgLteS>() //
|| is<DfgGtS>() //
|| is<DfgGteS>()) {
const DfgVertexBinary* const binp = as<DfgVertexBinary>();
const DfgVertex* const lhsp = binp->inputp(0);
const DfgVertex* const rhsp = binp->inputp(1);
return lhsp->width() <= VL_QUADSIZE && lhsp->is<DfgConst>() && rhsp->isCheaperThanLoad();
}
// Otherwise probably not
return false;
}
// }}}
// DfgGraph {{{
template <typename T_User>
DfgUserMap<T_User> DfgGraph::makeUserMap() const {
return DfgUserMap<T_User>{this};
}
// }}}
#endif