verilator/src/V3Graph.h

496 lines
19 KiB
C
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Graph optimizations
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// 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
//
//*************************************************************************
#ifndef VERILATOR_V3GRAPH_H_
#define VERILATOR_V3GRAPH_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include "V3List.h"
#include "V3Rtti.h"
#include <algorithm>
#include <functional>
class FileLine;
class V3Graph;
class V3GraphVertex;
class V3GraphEdge;
class GraphAcycEdge;
class OrderEitherVertex;
class OrderLogicVertex;
//=============================================================================
// Most graph algorithms accept an arbitrary function that returns
// True for those edges we should honor.
using V3EdgeFuncP = bool (*)(const V3GraphEdge* edgep);
//=============================================================================
// When the Graph represents a directional acyclical graph (DAG), following
// the to() edges is forward, and back() is reverse. However, sometimes
// it's useful to have algorithms that can walk in either direction, hence
// some methods take GraphWay to programmatically select the direction.
class GraphWay final {
public:
enum en : uint8_t {
FORWARD = 0,
REVERSE = 1,
NUM_WAYS = 2 // NUM_WAYS is not an actual way, it's typically
// // an array dimension or loop bound.
};
enum en m_e;
GraphWay()
: m_e{FORWARD} {}
// cppcheck-suppress noExplicitConstructor
constexpr GraphWay(en _e)
: m_e{_e} {}
explicit constexpr GraphWay(int _e)
2020-08-18 14:10:44 +02:00
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
const char* ascii() const {
static const char* const names[] = {"FORWARD", "REVERSE"};
return names[m_e];
}
// METHODS unique to this class
Improve performance of MTask coarsening Various optimizations to speed up MTasks coarsening (which is the long pole in the multi-threaded scheduling of very large designs). The biggest impact ones: - Use efficient hand written Pairing Heaps for implementing priority queues and the scoreboard, instead of the old SortByValueMap. This helps us avoid having to sort a lot of merge candidates that we will never actually consider and helps a lot in performance. - Remove unnecessary associative containers and store data structures (the heap nodes in particular) directly in the object they relate to. This eliminates a huge amount of lookups and helps a lot in performance. - Distribute storage for SiblingMC instances into the LogicMTask instances, and combine with the sibling maps. This again eliminates hash table lookups and makes storage structures smaller. - Remove some now bidirectional edge maps, keep only the forward map. There are also some other smaller optimizations: - Replaced more unnecessary dynamic_casts with static_casts - Templated some functions/classes to reduce the number of static branches in loops. - Improves sorting of edges for sibling candidate creation - Various micro-optimizations here and there This speeds up MTask coarsening by 3.8x on a large design, which translates to a 2.5x speedup of the ordering pass in multi-threaded mode. (Combined with the earlier optimizations, ordering is now 3x faster.) Due to the elimination of a lot of the auxiliary data structures, and ensuring a minimal size for the necessary ones, memory consumption of the MTask coarsening is also reduced (measured up to 4.4x reduction though the accuracy of this is low). The algorithm is identical except for minor alterations of the order some candidates are added or removed, this can cause perturbation in the output due to tied scores being broken based on IDs.
2022-08-07 15:11:58 +02:00
constexpr GraphWay invert() const { return GraphWay{m_e ^ 1}; }
constexpr bool forward() const { return m_e == FORWARD; }
constexpr bool reverse() const { return m_e != FORWARD; }
};
constexpr bool operator==(const GraphWay& lhs, const GraphWay& rhs) { return lhs.m_e == rhs.m_e; }
constexpr bool operator==(const GraphWay& lhs, GraphWay::en rhs) { return lhs.m_e == rhs; }
constexpr bool operator==(GraphWay::en lhs, const GraphWay& rhs) { return lhs == rhs.m_e; }
2024-03-26 00:06:25 +01:00
//============================================================================
// TODO should we have a smaller edge structure when we don't need weight etc?
class V3GraphEdge VL_NOT_FINAL {
VL_RTTI_IMPL_BASE(V3GraphEdge)
// Wires/variables aren't edges. Edges have only a single to/from vertex
public:
// ENUMS
enum Cutable : uint8_t { NOT_CUTABLE = false, CUTABLE = true }; // For passing to V3GraphEdge
protected:
friend class V3Graph;
friend class V3GraphVertex;
friend class GraphAcyc;
friend class GraphAcycEdge;
V3ListLinks<V3GraphEdge> m_oLinks; // List links to store instances of this class (out edges)
V3ListLinks<V3GraphEdge> m_iLinks; // List links to store instances of this class (in edges)
//
V3GraphVertex* m_fromp; // Vertices pointing to this edge
V3GraphVertex* m_top; // Vertices this edge points to
int m_weight; // Weight of the connection
bool m_cutable; // Interconnect may be broken in order sorting
union {
void* m_userp; // Marker for some algorithms
uint64_t m_user; // Marker for some algorithms
};
// METHODS
void init(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight,
bool cutable = false) VL_MT_DISABLED;
void cut() { m_weight = 0; } // 0 weight is same as disconnected
// CONSTRUCTORS
protected:
V3GraphEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top,
const V3GraphEdge& old) VL_MT_DISABLED {
init(graphp, fromp, top, old.m_weight, old.m_cutable);
}
private:
V3ListLinks<V3GraphEdge>& oLinks() { return m_oLinks; }
V3ListLinks<V3GraphEdge>& iLinks() { return m_iLinks; }
public:
// List types to store instances of this class
using OList = V3List<V3GraphEdge, &V3GraphEdge::oLinks>;
using IList = V3List<V3GraphEdge, &V3GraphEdge::iLinks>;
//! Add DAG from one node to the specified node
V3GraphEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight,
bool cutable = false) VL_MT_DISABLED {
init(graphp, fromp, top, weight, cutable);
}
//! Clone copy constructor. Doesn't copy existing vertices or user/userp.
virtual V3GraphEdge* clone(V3Graph* graphp, V3GraphVertex* fromp,
V3GraphVertex* top) const VL_MT_DISABLED {
return new V3GraphEdge{graphp, fromp, top, *this};
}
virtual ~V3GraphEdge() = default;
// METHODS
// Return true iff of type T
template <typename T>
bool is() const {
static_assert(std::is_base_of<V3GraphEdge, T>::value,
"'T' must be a subtype of V3GraphEdge");
return V3Rtti::isInstanceOf<T>(this);
2024-03-26 00:06:25 +01:00
}
// Return cast to subtype T and assert of that type
template <typename T>
T* as() {
UASSERT(is<T>(), "V3GraphEdge is not of expected type");
return static_cast<T*>(this);
}
template <typename T>
const T* as() const {
UASSERT(is<T>(), "V3GraphEdge is not of expected type");
return static_cast<const T*>(this);
}
// Return cast to subtype T, else nullptr if different type
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;
}
virtual string name() const VL_MT_DISABLED;
virtual string dotLabel() const { return ""; }
virtual string dotColor() const { return cutable() ? "yellowGreen" : "red"; }
virtual string dotStyle() const { return cutable() ? "dashed" : ""; }
virtual int sortCmp(const V3GraphEdge* rhsp) const VL_MT_DISABLED;
void unlinkDelete() VL_MT_DISABLED;
void relinkFromp(V3GraphVertex* newFromp) VL_MT_DISABLED;
void relinkTop(V3GraphVertex* newTop) VL_MT_DISABLED;
// ACCESSORS
int weight() const { return m_weight; }
void weight(int weight) { m_weight = weight; }
bool cutable() const { return m_cutable; }
void cutable(bool cutable) { m_cutable = cutable; }
void userp(void* user) { m_userp = user; }
void* userp() const { return m_userp; }
void user(uint64_t user) { m_user = user; }
uint64_t user() const { return m_user; }
V3GraphVertex* fromp() const { return m_fromp; }
V3GraphVertex* top() const { return m_top; }
template <GraphWay::en N_Way>
2024-03-26 00:06:25 +01:00
V3GraphVertex* furtherp() const {
return N_Way == GraphWay::FORWARD ? top() : fromp();
2024-03-26 00:06:25 +01:00
}
// STATIC ACCESSORS
static bool followNotCutable(const V3GraphEdge* edgep) { return !edgep->m_cutable; }
static bool followAlwaysTrue(const V3GraphEdge*) { return true; }
};
//============================================================================
class V3GraphVertex VL_NOT_FINAL {
VL_RTTI_IMPL_BASE(V3GraphVertex)
friend class V3Graph;
friend class V3GraphEdge;
friend class GraphAcyc;
friend class GraphAlgRank;
friend class GraphAlgRankDepth;
2024-03-26 00:06:25 +01:00
V3ListLinks<V3GraphVertex> m_links; // List links to store instances of this class
V3GraphEdge::OList m_outs; // List of outbound edges
V3GraphEdge::IList m_ins; // List of inbound edges
double m_fanout; // Order fanout
uint32_t m_color; // Color of the node
uint32_t m_rank; // Rank of edge
union {
void* m_userp; // Marker for some algorithms
uint32_t m_user; // Marker for some algorithms
};
// ACCESSORS
void fanout(double fanout) { m_fanout = fanout; }
protected:
// CONSTRUCTORS
V3GraphVertex(V3Graph* graphp, const V3GraphVertex& old) VL_MT_DISABLED;
private:
V3ListLinks<V3GraphVertex>& links() { return m_links; }
public:
// List types to store instances of this class
using List = V3List<V3GraphVertex, &V3GraphVertex::links>;
explicit V3GraphVertex(V3Graph* graphp) VL_MT_DISABLED;
//! Clone copy constructor. Doesn't copy edges or user/userp.
virtual V3GraphVertex* clone(V3Graph* graphp) const VL_MT_DISABLED {
return new V3GraphVertex{graphp, *this};
}
virtual ~V3GraphVertex() = default;
void unlinkEdges(V3Graph* graphp) VL_MT_DISABLED;
void unlinkDelete(V3Graph* graphp) VL_MT_DISABLED;
// METHODS
// Return true iff of type T
template <typename T>
bool is() const {
static_assert(std::is_base_of<V3GraphVertex, T>::value,
"'T' must be a subtype of V3GraphVertex");
return V3Rtti::isInstanceOf<T>(this);
2024-03-26 00:06:25 +01:00
}
// Return cast to subtype T and assert of that type
template <typename T>
T* as() {
UASSERT_OBJ(is<T>(), this, "V3GraphVertex is not of expected type");
return static_cast<T*>(this);
}
template <typename T>
const T* as() const {
UASSERT_OBJ(is<T>(), this, "V3GraphVertex is not of expected type");
return static_cast<const T*>(this);
}
// Return cast to subtype T, else nullptr if different type
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;
}
// ACCESSORS
virtual string name() const { return ""; }
virtual string dotColor() const { return "black"; }
virtual string dotShape() const { return ""; }
virtual string dotStyle() const { return ""; }
virtual string dotName() const { return ""; }
virtual string dotRank() const { return ""; }
virtual uint32_t rankAdder() const { return 1; }
virtual FileLine* fileline() const { return nullptr; } // nullptr for unknown
virtual int sortCmp(const V3GraphVertex* rhsp) const {
// LHS goes first if of lower rank, or lower fanout
if (m_rank < rhsp->m_rank) return -1;
if (m_rank > rhsp->m_rank) return 1;
if (m_fanout < rhsp->m_fanout) return -1;
if (m_fanout > rhsp->m_fanout) return 1;
return 0;
}
uint32_t color() const { return m_color; }
void color(uint32_t color) { m_color = color; }
uint32_t rank() const { return m_rank; }
void rank(uint32_t rank) { m_rank = rank; }
double fanout() const { return m_fanout; }
void user(uint32_t user) { m_user = user; }
uint32_t user() const VL_MT_STABLE { return m_user; }
void userp(void* userp) { m_userp = userp; }
void* userp() const VL_MT_STABLE { return m_userp; }
V3GraphEdge::IList& inEdges() { return m_ins; }
const V3GraphEdge::IList& inEdges() const { return m_ins; }
template <GraphWay::en N_Way>
2024-03-26 00:06:25 +01:00
inline auto& edges();
template <GraphWay::en N_Way>
2024-03-26 00:06:25 +01:00
inline const auto& edges() const;
bool inEmpty() const { return m_ins.empty(); }
bool inSize1() const { return m_ins.hasSingleElement(); }
V3GraphEdge::OList& outEdges() { return m_outs; }
const V3GraphEdge::OList& outEdges() const { return m_outs; }
bool outEmpty() const { return m_outs.empty(); }
bool outSize1() const { return m_outs.hasSingleElement(); }
// METHODS
/// Error reporting
void v3errorEnd(const std::ostringstream& str) const
VL_RELEASE(V3Error::s().m_mutex) VL_MT_DISABLED;
void v3errorEndFatal(const std::ostringstream& str) const
2024-03-26 00:06:25 +01:00
VL_RELEASE(V3Error::s().m_mutex) VL_MT_DISABLED;
/// Edges are routed around this vertex to point from "from" directly to "to"
void rerouteEdges(V3Graph* graphp) VL_MT_DISABLED;
// Find the edge connecting this vertex to the given vertex.
// If edge is not found returns nullptr. O(edges) performance.
template <GraphWay::en N_Way>
2024-03-26 00:06:25 +01:00
V3GraphEdge* findConnectingEdgep(V3GraphVertex* otherp) VL_MT_DISABLED;
};
std::ostream& operator<<(std::ostream& os, V3GraphVertex* vertexp) VL_MT_DISABLED;
template <>
inline auto& V3GraphVertex::edges<GraphWay::FORWARD>() {
return m_outs;
}
template <>
inline auto& V3GraphVertex::edges<GraphWay::REVERSE>() {
return m_ins;
}
template <>
inline const auto& V3GraphVertex::edges<GraphWay::FORWARD>() const {
return m_outs;
}
template <>
inline const auto& V3GraphVertex::edges<GraphWay::REVERSE>() const {
return m_ins;
}
//============================================================================
class V3Graph VL_NOT_FINAL {
// MEMBERS
2024-03-26 00:06:25 +01:00
V3GraphVertex::List m_vertices; // All vertices
protected:
friend class V3GraphVertex;
friend class V3GraphEdge;
friend class GraphAcyc;
// METHODS
double orderDFSIterate(V3GraphVertex* vertexp) VL_MT_DISABLED;
2024-03-26 00:06:25 +01:00
void dumpEdge(std::ostream& os, const V3GraphVertex& vertex,
const V3GraphEdge& edge) const VL_MT_DISABLED;
// ACCESSORS
public:
V3Graph() VL_MT_DISABLED;
virtual ~V3Graph() VL_MT_DISABLED;
virtual string dotRankDir() const { return "TB"; } // rankdir for dot plotting
// METHODS
void clear() VL_MT_DISABLED; // Empty it of all vertices/edges, as if making a new object
bool empty() const { return m_vertices.empty(); }
2024-03-26 00:06:25 +01:00
V3GraphVertex::List& vertices() { return m_vertices; }
const V3GraphVertex::List& vertices() const { return m_vertices; }
// METHODS - ALGORITHMS
/// Clears color
void clearColors() VL_MT_DISABLED;
/// Assign same color to all vertices in the same weakly connected component
/// Thus different color if there's no edges between the two subgraphs
void weaklyConnected(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Assign same color to all vertices that are strongly connected
/// Thus different color if there's no directional circuit within the subgraphs.
/// (I.E. all loops will occur within each color, not between them.)
/// Side-effect: changes user()
void stronglyConnected(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
2022-12-10 03:06:27 +01:00
/// Assign an ordering number to all vertexes in a tree.
/// For multiple usages of a vertex, get the maximum of all inbound ranks + 1
/// All nodes with no inputs will get rank 1
/// Side-effect: changes user()
void rank(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
void rank() VL_MT_DISABLED;
/// Assign an ordering number to all vertexes in a tree.
/// For multiple usages of a vertex, get the minimu of all inbound ranks + 1
/// All nodes with no inputs will get rank 1
/// Side-effect: changes user()
void rankMin(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
void rankMin() VL_MT_DISABLED;
/// Sort all vertices and edges using the V3GraphVertex::sortCmp() function
void sortVertices() VL_MT_DISABLED;
/// Sort all edges and edges using the V3GraphEdge::sortCmp() function
void sortEdges() VL_MT_DISABLED;
/// Order all vertices by rank and fanout, lowest first
/// Sort all vertices by rank and fanout, lowest first
/// Sort all edges by weight, lowest first
/// Side-effect: assigns ranks to every node, and changes user()
void order() VL_MT_DISABLED;
/// Similar to order() but does not assign ranks. Caller must
/// ensure that the graph has been ranked ahead of the call.
/// Side-effect: assigns ranks to every node, and changes user()
void orderPreRanked() VL_MT_DISABLED;
/// Make acyclical (into a tree) by breaking a minimal subset of cutable edges.
/// Side-effect: changes rank(), changes user()
void acyclic(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Remove any redundant edges, weights become MAX of any other weight
/// Side-effect: changes user()
void removeRedundantEdgesMax(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Remove any redundant edges, weights become SUM of any other weight
/// Side-effect: changes user()
void removeRedundantEdgesSum(V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
/// Remove any transitive edges. E.g. if have edges A->B, B->C, and A->C
/// then A->C is a "transitive" edge; it's implied by the first two
/// (assuming the DAG is a dependency graph.)
/// This algorithm can be expensive.
void removeTransitiveEdges() VL_MT_DISABLED;
/// Call loopsVertexCb on any one loop starting where specified
/// Side-effect: changes user()
string reportLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp) VL_MT_DISABLED;
/// Build a subgraph of all loops starting where specified
/// Side-effect: changes user()
void subtreeLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp,
V3Graph* loopGraphp) VL_MT_DISABLED;
/// Clear user()
void userClearVertices() VL_MT_DISABLED;
void userClearEdges() VL_MT_DISABLED;
/// Debugging
void dump(std::ostream& os = std::cout) const VL_MT_DISABLED;
void dumpDotFile(const string& filename, bool colorAsSubgraph) const VL_MT_DISABLED;
void dumpDotFilePrefixed(const string& nameComment,
bool colorAsSubgraph = false) const VL_MT_DISABLED;
void dumpDotFilePrefixedAlways(const string& nameComment,
bool colorAsSubgraph = false) const VL_MT_DISABLED;
2024-03-26 00:06:25 +01:00
void dumpEdges(std::ostream& os, const V3GraphVertex& vertex) const VL_MT_DISABLED;
static void selfTest() VL_MT_DISABLED;
class ParallelismReport final {
friend class GraphAlgParallelismReport;
// Total cost of evaluating the whole graph. The ratio of m_totalGraphCost to
// m_criticalPathCost gives us an estimate of the parallelizability of this graph which is
// only as good as the guess returned by vertexCost.
uint64_t m_totalGraphCost = 0;
// Cost of the critical path, in abstract units (the same units returned by the vertexCost)
uint64_t m_criticalPathCost = 0;
uint64_t m_vertexCount = 0; // Number of vertexes in the graph
uint64_t m_edgeCount = 0; // Number of edges in the grap
ParallelismReport() = default;
public:
~ParallelismReport() = default;
ParallelismReport(const ParallelismReport&) = default;
ParallelismReport& operator=(const ParallelismReport&) = default;
uint64_t totalGraphCost() const { return m_totalGraphCost; }
uint64_t criticalPathCost() const { return m_criticalPathCost; }
uint64_t vertexCount() const { return m_vertexCount; }
uint64_t edgeCount() const { return m_edgeCount; }
double parallelismFactor() const {
return (static_cast<double>(m_totalGraphCost) / m_criticalPathCost);
}
};
2024-03-26 00:06:25 +01:00
ParallelismReport
parallelismReport(std::function<uint64_t(const V3GraphVertex*)> vertexCost) VL_MT_DISABLED;
// CALLBACKS
virtual void loopsMessageCb(V3GraphVertex* vertexp, V3EdgeFuncP edgeFuncp) VL_MT_DISABLED;
virtual string loopsVertexCb(V3GraphVertex* vertexp) VL_MT_DISABLED;
};
//============================================================================
#endif // Guard