verilator/src/V3Dfg.h

842 lines
33 KiB
C++

// -*- 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 <algorithm>
#include <array>
#include <functional>
#include <new>
#include <type_traits>
#include <unordered_map>
#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;
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<DfgEdge> m_links; // V3List links in the list of sinks of m_srcp
DfgEdge() = delete;
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(); }
// 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 <typename, bool>
friend class DfgUserMap;
// STATE
V3ListLinks<DfgVertex> m_links; // V3List links in the DfgGraph
std::vector<std::unique_ptr<DfgEdge>> 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<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) 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<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.
bool foreachSource(std::function<bool(DfgVertex&)> f) {
for (const std::unique_ptr<DfgEdge>& 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<bool(const DfgVertex&)> f) const {
for (const std::unique_ptr<DfgEdge>& 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<bool(DfgVertex&)> 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<bool(const DfgVertex&)> 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 <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;
};
// 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<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
// 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 <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<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 (DfgConst* const cVtxp = vtx.cast<DfgConst>()) {
m_constVertices.linkBack(cVtxp);
} else if (DfgVertexVar* const vVtxp = vtx.cast<DfgVertexVar>()) {
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<DfgConst>()) {
m_constVertices.unlink(cVtxp);
} else if (DfgVertexVar* const vVtxp = vtx.cast<DfgVertexVar>()) {
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<void(DfgVertex&)> 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<void(const DfgVertex&)> 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<DfgGraph> 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<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;
};
//------------------------------------------------------------------------------
// 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); }
};
//------------------------------------------------------------------------------
// 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 {{{
bool DfgVertex::isCheaperThanLoad() const {
// Array sels are just address computation
if (is<DfgArraySel>()) return true;
// Small constant 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);
}
// 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 lCatp = catp->lhsp()->cast<DfgConst>();
if (!lCatp) return false;
if (!lCatp->isZero()) return false;
return catp->rhsp()->isCheaperThanLoad();
}
// Otherwise probably not
return false;
}
// }}}
// DfgGraph {{{
template <typename T_User>
DfgUserMap<T_User> DfgGraph::makeUserMap() const {
return DfgUserMap<T_User>{this};
}
// }}}
#endif