842 lines
33 KiB
C++
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
|