Use the same serial ordering within MTasks as we use in serial mode (#4994)

The goal here is to use as single ordering heuristic (which can be
improved later) within MTasks as we do for serial code ordering. The
heuristic itself is factored out into the new OrderMoveGraphSerializer.
This also yields slightly nicer ordering than the previously use
GraphStream, so we end up with fewer trigger (domain) conditionals in
the MTasks, this can be worth a few percent speedup.

This has the somewhat nice side-effect of reusing OrderMoveGraphVertex
for both serial and parallel mode, so MTaskMoveGraphVertex can be
removed.

Serial mode yields identical output.
This commit is contained in:
Geza Lore
2024-03-17 13:15:39 +00:00
committed by GitHub
parent 494e05b326
commit 6ffff8565f
8 changed files with 555 additions and 506 deletions
+45 -265
View File
@@ -20,283 +20,63 @@
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Graph.h"
#include "V3List.h"
#include "V3OrderCFuncEmitter.h"
#include "V3OrderInternal.h"
#include "V3OrderMoveGraphBuilder.h"
#include "V3OrderMoveGraph.h"
#include <memory>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
class OrderMoveVertex;
// Information stored for each unique (domain, scope) pair. Mainly a list of ready vertices under
// that (domain, scope). OrderMoveDomScope instances are themselves organized into a global ready
// list if they have ready vertices.
class OrderMoveDomScope final {
// STATE
V3List<OrderMoveVertex*> m_readyVertices; // Ready vertices in this domain/scope
V3ListEnt<OrderMoveDomScope*> m_listEnt; // List entry to store this instance
bool m_isOnList = false; // True if DomScope is already on a list through m_listEnt
const AstSenTree* const m_domainp; // Domain the vertices belong to
const AstScope* const m_scopep; // Scope the vertices belong to
// Key type for map below
class DomScopeMapKey final {
const AstSenTree* const m_domainp;
const AstScope* const m_scopep;
public:
DomScopeMapKey(const AstSenTree* domainp, const AstScope* scopep)
: m_domainp{domainp}
, m_scopep{scopep} {}
struct Hash final {
size_t operator()(const DomScopeMapKey& key) const {
V3Hash hash{reinterpret_cast<uintptr_t>(key.m_domainp)};
hash += reinterpret_cast<uintptr_t>(key.m_scopep);
return hash.value();
}
};
struct Equal final {
bool operator()(const DomScopeMapKey& a, const DomScopeMapKey& b) const {
return a.m_domainp == b.m_domainp && a.m_scopep == b.m_scopep;
}
};
};
using DomScopeMap = std::unordered_map<DomScopeMapKey, OrderMoveDomScope, DomScopeMapKey::Hash,
DomScopeMapKey::Equal>;
// Map from Domain/Scope to the corresponding DomScope instance
static DomScopeMap s_dsMap;
public:
// STATIC MEMBERS
static OrderMoveDomScope& getOrCreate(const AstSenTree* domainp, const AstScope* scopep) {
return s_dsMap
.emplace(std::piecewise_construct, //
std::forward_as_tuple(domainp, scopep), //
std::forward_as_tuple(domainp, scopep))
.first->second;
}
static void clear() { s_dsMap.clear(); }
// CONSTRUCTOR
OrderMoveDomScope(const AstSenTree* domainp, const AstScope* scopep)
: m_domainp{domainp}
, m_scopep{scopep} {}
~OrderMoveDomScope() = default;
VL_UNCOPYABLE(OrderMoveDomScope);
VL_UNMOVABLE(OrderMoveDomScope);
// MEMBERS
V3List<OrderMoveVertex*>& readyVertices() { return m_readyVertices; }
const AstSenTree* domainp() const { return m_domainp; }
const AstScope* scopep() const { return m_scopep; }
bool isOnList() const { return m_isOnList; }
void unlinkFrom(V3List<OrderMoveDomScope*>& list) {
UASSERT_OBJ(m_isOnList, m_domainp, "unlinkFrom, but DomScope is not on a list");
m_isOnList = false;
m_listEnt.unlink(list, this);
}
void appendTo(V3List<OrderMoveDomScope*>& list) {
UASSERT_OBJ(!m_isOnList, m_domainp, "appendTo, but DomScope is already on a list");
m_isOnList = true;
m_listEnt.pushBack(list, this);
}
OrderMoveDomScope* nextp() const { return m_listEnt.nextp(); }
};
OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap;
// ######################################################################
// OrderMoveVertex constructor
class OrderMoveVertex final : public V3GraphVertex {
VL_RTTI_IMPL(OrderMoveVertex, V3GraphVertex)
// The corresponding logic vertex, or nullptr if this MoveVertex stands for a variable vertex.
OrderLogicVertex* const m_logicp;
OrderMoveDomScope& m_domScope; // DomScope this vertex is under
V3ListEnt<OrderMoveVertex*> m_listEnt; // List entry for ready list under DomScope
// METHODS
std::string dotColor() const override { return logicp() ? logicp()->dotColor() : ""; }
std::string name() const override VL_MT_STABLE {
if (!logicp()) {
return "var";
} else {
std::string nm = logicp()->name() + "\\n";
nm += "MV:";
nm += +" d=" + cvtToHex(logicp()->domainp());
nm += +" s=" + cvtToHex(logicp()->scopep());
return nm;
}
}
public:
// CONSTRUCTORS
OrderMoveVertex(V3Graph& graph, OrderLogicVertex* lVtxp,
const AstSenTree* domainp) VL_MT_DISABLED
: V3GraphVertex{&graph},
m_logicp{lVtxp},
m_domScope{OrderMoveDomScope::getOrCreate(domainp, lVtxp ? lVtxp->scopep() : nullptr)} {
UASSERT_OBJ(!lVtxp || lVtxp->domainp() == domainp, lVtxp, "Wrong domain for Move vertex");
}
~OrderMoveVertex() override = default;
OrderLogicVertex* logicp() const VL_MT_STABLE { return m_logicp; }
OrderMoveDomScope& domScope() const { return m_domScope; }
void unlinkFrom(V3List<OrderMoveVertex*>& list) { m_listEnt.unlink(list, this); }
void appendTo(V3List<OrderMoveVertex*>& list) { m_listEnt.pushBack(list, this); }
};
//######################################################################
// OrderSerial class
class OrderSerial final {
// STATE
std::unique_ptr<V3Graph> m_moveGraphp; // Graph of logic elements to move
V3List<OrderMoveDomScope*> m_readyDomScopeps; // List of DomScopes which have ready vertices
V3OrderCFuncEmitter m_emitter; // Code emitter to construct the result
// METHODS
// Take the given waiting logic vertex, and move it to the ready list its DomScope
void logicReady(OrderMoveVertex* lVtxp) {
UASSERT_OBJ(lVtxp->logicp(), lVtxp, "logicReady called on variable vertex");
UASSERT_OBJ(lVtxp->inEmpty(), lVtxp, "logicReady called on vertex with incoming edge");
// Add this logic vertex to the ready list of its DomScope
OrderMoveDomScope& domScope = lVtxp->domScope();
lVtxp->appendTo(domScope.readyVertices());
// Add the DomScope to the global ready list if not there yet
if (!domScope.isOnList()) domScope.appendTo(m_readyDomScopeps);
}
// Remove the given variable vertex, and check if any of its dependents are ready
void varReady(OrderMoveVertex* vVtxp) {
UASSERT_OBJ(!vVtxp->logicp(), vVtxp, "varReady called on logic vertex");
UASSERT_OBJ(vVtxp->inEmpty(), vVtxp, "varReady called on vertex with incoming edge");
// Remove dependency of consumer logic on this variable, and mark them ready if this is
// the last dependency.
for (V3GraphEdge *edgep = vVtxp->outBeginp(), *nextp; edgep; edgep = nextp) {
// Pick up next as we are deleting it
nextp = edgep->outNextp();
// The dependent logic
OrderMoveVertex* const lVtxp = edgep->top()->as<OrderMoveVertex>();
UASSERT_OBJ(lVtxp->logicp(), lVtxp, "The move graph should be bipartite");
// Delete this edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
// If this was the last dependency, the consumer logic is ready
if (lVtxp->inEmpty()) logicReady(lVtxp);
}
// Can delete the vertex now
VL_DO_DANGLING(vVtxp->unlinkDelete(m_moveGraphp.get()), vVtxp);
}
void process(const OrderGraph& orderGraph, const std::string& tag,
const V3Order::TrigToSenMap& trigToSen) {
// Build the move graph
m_moveGraphp = V3OrderMoveGraphBuilder<OrderMoveVertex>::apply(orderGraph, trigToSen);
if (dumpGraphLevel() >= 9) m_moveGraphp->dumpDotFilePrefixed(tag + "_ordermv_start");
m_moveGraphp->removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue);
if (dumpGraphLevel() >= 4) m_moveGraphp->dumpDotFilePrefixed(tag + "_ordermv_simpl");
// Mark initially ready vertices (those with no dependencies)
for (V3GraphVertex* vtxp = m_moveGraphp->verticesBeginp(); vtxp;
vtxp = vtxp->verticesNextp()) {
if (!vtxp->inEmpty()) continue;
OrderMoveVertex* const mVtxp = vtxp->as<OrderMoveVertex>();
if (mVtxp->logicp()) {
logicReady(mVtxp);
} else {
varReady(mVtxp);
}
}
// Emit all logic as they become ready
for (OrderMoveDomScope *currDomScopep = m_readyDomScopeps.begin(), *nextDomScopep;
currDomScopep; currDomScopep = nextDomScopep) {
m_emitter.forceNewFunction();
// Emit all logic ready under the current DomScope
V3List<OrderMoveVertex*>& currReadyList = currDomScopep->readyVertices();
UASSERT(!currReadyList.empty(), "DomScope on ready list, not has no ready vertices");
while (OrderMoveVertex* const lVtxp = currReadyList.begin()) {
UASSERT_OBJ(&lVtxp->domScope() == currDomScopep, lVtxp, "DomScope mismatch");
// Unlink vertex from ready list under the DomScope
lVtxp->unlinkFrom(currReadyList);
// Unlink DomScope from the global ready list if this is the last vertex
// TODO: should do this later
if (currReadyList.empty()) currDomScopep->unlinkFrom(m_readyDomScopeps);
// Actually emit the logic under this vertex
m_emitter.emitLogic(lVtxp->logicp());
// Remove dependency of produced variables on this logic, and mark them ready if
// this is the last producer.
for (V3GraphEdge *edgep = lVtxp->outBeginp(), *nextp; edgep; edgep = nextp) {
// Pick up next as we are deleting it
nextp = edgep->outNextp();
// The dependent variable
OrderMoveVertex* const vVtxp = edgep->top()->as<OrderMoveVertex>();
UASSERT_OBJ(!vVtxp->logicp(), vVtxp, "The move graph should be bipartite");
// Delete this edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
// If this was the last producer, the produced variable is ready
if (vVtxp->inEmpty()) varReady(vVtxp);
}
// Can delete the vertex now
VL_DO_DANGLING(lVtxp->unlinkDelete(m_moveGraphp.get()), lVtxp);
}
// Done with this DomScope, pick a new one to emit. Prefer a new scope under the
// same domain. If there isn't one, just pick teh head of the global ready list
nextDomScopep = m_readyDomScopeps.begin();
for (OrderMoveDomScope* huntp = nextDomScopep; huntp; huntp = huntp->nextp()) {
if (huntp->domainp() == currDomScopep->domainp()) {
nextDomScopep = huntp;
break;
}
}
}
UASSERT(m_moveGraphp->empty(), "Waiting vertices remain, but none are ready");
}
// CONSTRUCTOR
OrderSerial(const OrderGraph& orderGraph, const std::string& tag,
const V3Order::TrigToSenMap& trigToSen, bool slow)
: m_emitter{tag, slow} {
OrderMoveDomScope::clear();
process(orderGraph, tag, trigToSen);
OrderMoveDomScope::clear();
}
~OrderSerial() = default;
public:
// Order the logic
static std::vector<AstActive*> apply(const OrderGraph& graph, const std::string& tag,
const V3Order::TrigToSenMap& trigToSen, bool slow) {
return OrderSerial{graph, tag, trigToSen, slow}.m_emitter.getAndClearActiveps();
}
};
std::vector<AstActive*> V3Order::createSerial(const OrderGraph& graph, const std::string& tag,
const TrigToSenMap& trigToSen, bool slow) {
UINFO(2, " Constructing serial code for '" + tag + "'");
return OrderSerial::apply(graph, tag, trigToSen, slow);
// Build the move graph
OrderMoveDomScope::clear();
const std::unique_ptr<OrderMoveGraph> moveGraphp = OrderMoveGraph::build(graph, trigToSen);
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv");
// Serializer
OrderMoveGraphSerializer serializer{*moveGraphp};
// Add initially ready vertices (those with no dependencies) to the serializer as seeds
for (V3GraphVertex *vtxp = moveGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNextp();
if (vtxp->inEmpty()) serializer.addSeed(vtxp->as<OrderMoveVertex>());
}
// Emit all logic as they become ready
V3OrderCFuncEmitter emitter{tag, slow};
OrderMoveDomScope* prevDomScopep = nullptr;
while (OrderMoveVertex* const mVtxp = serializer.getNext()) {
// We only really care about logic vertices
if (OrderLogicVertex* const logicp = mVtxp->logicp()) {
// Force a new function if the domain or scope changed, for better combining.
OrderMoveDomScope* const domScopep = &mVtxp->domScope();
if (domScopep != prevDomScopep) emitter.forceNewFunction();
prevDomScopep = domScopep;
// Emit the logic under this vertex
emitter.emitLogic(logicp);
}
// Can delete the vertex now
VL_DO_DANGLING(mVtxp->unlinkDelete(moveGraphp.get()), mVtxp);
}
// Delete the remaining variable vertices
for (V3GraphVertex *vtxp = moveGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) {
nextp = vtxp->verticesNextp();
if (!vtxp->as<OrderMoveVertex>()->logicp()) {
VL_DO_DANGLING(vtxp->unlinkDelete(moveGraphp.get()), vtxp);
}
}
UASSERT(moveGraphp->empty(), "Waiting vertices remain, but none are ready");
OrderMoveDomScope::clear();
return emitter.getAndClearActiveps();
}