2022-10-08 15:34:07 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: DfgGraph decomposition algorithms
|
|
|
|
|
//
|
|
|
|
|
// 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
|
2022-10-08 15:34:07 +02:00
|
|
|
// 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
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Algorithms that take a DfgGraph and decompose it into multiple DfgGraphs.
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2022-10-08 15:34:07 +02:00
|
|
|
#include "V3Dfg.h"
|
2025-08-05 14:03:30 +02:00
|
|
|
#include "V3DfgPasses.h"
|
2022-10-08 15:34:07 +02:00
|
|
|
#include "V3File.h"
|
|
|
|
|
|
|
|
|
|
#include <deque>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
|
|
|
|
class SplitIntoComponents final {
|
|
|
|
|
|
|
|
|
|
// STATE
|
|
|
|
|
DfgGraph& m_dfg; // The input graph
|
|
|
|
|
const std::string m_prefix; // Component name prefix
|
|
|
|
|
std::vector<std::unique_ptr<DfgGraph>> m_components; // The extracted components
|
|
|
|
|
// Component counter - starting from 1 as 0 is the default value used as a marker
|
|
|
|
|
size_t m_componentCounter = 1;
|
|
|
|
|
|
|
|
|
|
void colorComponents() {
|
|
|
|
|
// Work queue for depth first traversal starting from this vertex
|
|
|
|
|
std::vector<DfgVertex*> queue;
|
|
|
|
|
queue.reserve(m_dfg.size());
|
|
|
|
|
|
|
|
|
|
// any sort of interesting logic must involve a variable, so we only need to iterate them
|
2024-03-26 00:06:25 +01:00
|
|
|
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
|
2022-10-08 15:34:07 +02:00
|
|
|
// If already assigned this vertex to a component, then continue
|
2024-03-26 00:06:25 +01:00
|
|
|
if (vtx.user<size_t>()) continue;
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Start depth first traversal at this vertex
|
2024-03-26 00:06:25 +01:00
|
|
|
queue.push_back(&vtx);
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Depth first traversal
|
|
|
|
|
do {
|
|
|
|
|
// Pop next work item
|
|
|
|
|
DfgVertex& item = *queue.back();
|
|
|
|
|
queue.pop_back();
|
|
|
|
|
|
|
|
|
|
// Move on if already visited
|
|
|
|
|
if (item.user<size_t>()) continue;
|
|
|
|
|
|
|
|
|
|
// Assign to current component
|
|
|
|
|
item.user<size_t>() = m_componentCounter;
|
|
|
|
|
|
|
|
|
|
// Enqueue all sources and sinks of this vertex.
|
|
|
|
|
item.forEachSource([&](DfgVertex& src) { queue.push_back(&src); });
|
|
|
|
|
item.forEachSink([&](DfgVertex& dst) { queue.push_back(&dst); });
|
|
|
|
|
} while (!queue.empty());
|
|
|
|
|
|
|
|
|
|
// Done with this component
|
|
|
|
|
++m_componentCounter;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 00:06:25 +01:00
|
|
|
template <typename Vertex>
|
|
|
|
|
void moveVertices(DfgVertex::List<Vertex>& list) {
|
|
|
|
|
for (DfgVertex* const vtxp : list.unlinkable()) {
|
|
|
|
|
if (const size_t component = vtxp->user<size_t>()) {
|
|
|
|
|
m_dfg.removeVertex(*vtxp);
|
|
|
|
|
m_components[component - 1]->addVertex(*vtxp);
|
2022-10-08 15:34:07 +02:00
|
|
|
} else {
|
|
|
|
|
// This vertex is not connected to a variable and is hence unused, remove here
|
2024-03-26 00:06:25 +01:00
|
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-27 11:52:40 +01:00
|
|
|
SplitIntoComponents(DfgGraph& dfg, const std::string& label)
|
2022-10-08 15:34:07 +02:00
|
|
|
: m_dfg{dfg}
|
|
|
|
|
, m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
|
|
|
|
|
// Component number is stored as DfgVertex::user<size_t>()
|
|
|
|
|
const auto userDataInUse = m_dfg.userDataInUse();
|
|
|
|
|
// Color each component of the graph
|
|
|
|
|
colorComponents();
|
|
|
|
|
// Allocate the component graphs
|
|
|
|
|
m_components.resize(m_componentCounter - 1);
|
|
|
|
|
for (size_t i = 1; i < m_componentCounter; ++i) {
|
2025-07-01 23:55:08 +02:00
|
|
|
m_components[i - 1].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i - 1)});
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
// Move the vertices to the component graphs
|
2024-03-26 00:06:25 +01:00
|
|
|
moveVertices(m_dfg.varVertices());
|
|
|
|
|
moveVertices(m_dfg.constVertices());
|
|
|
|
|
moveVertices(m_dfg.opVertices());
|
2022-10-08 15:34:07 +02:00
|
|
|
//
|
|
|
|
|
UASSERT(m_dfg.size() == 0, "'this' DfgGraph should have been emptied");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static std::vector<std::unique_ptr<DfgGraph>> apply(DfgGraph& dfg, const std::string& label) {
|
|
|
|
|
return std::move(SplitIntoComponents{dfg, label}.m_components);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<DfgGraph>> DfgGraph::splitIntoComponents(std::string label) {
|
|
|
|
|
return SplitIntoComponents::apply(*this, label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ExtractCyclicComponents final {
|
|
|
|
|
// TYPES
|
2025-08-05 14:03:30 +02:00
|
|
|
// We reuse the DfgVertex::user state set by V3DfgPasses::colorStronglyConnectedComponents.
|
|
|
|
|
// We sneak an extra flag into the MSB to indicate the vertex was merged already.
|
|
|
|
|
class VertexState final {
|
|
|
|
|
uint64_t& m_userr;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
VertexState(DfgVertex& vtx)
|
|
|
|
|
: m_userr{vtx.getUser<uint64_t>()} {}
|
|
|
|
|
bool merged() const { return m_userr >> 63; }
|
|
|
|
|
void setMerged() { m_userr |= 1ULL << 63; }
|
|
|
|
|
uint64_t component() const { return m_userr & ~(1ULL << 63); }
|
|
|
|
|
void component(uint64_t value) { m_userr = (m_userr & (1ULL << 63)) | value; }
|
2022-10-08 15:34:07 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// STATE
|
|
|
|
|
DfgGraph& m_dfg; // The input graph
|
|
|
|
|
const std::string m_prefix; // Component name prefix
|
|
|
|
|
const bool m_doExpensiveChecks = v3Global.opt.debugCheck();
|
|
|
|
|
// The extracted cyclic components
|
|
|
|
|
std::vector<std::unique_ptr<DfgGraph>> m_components;
|
|
|
|
|
// Map from 'variable vertex' -> 'component index' -> 'clone in that component'
|
2025-08-05 14:03:30 +02:00
|
|
|
std::unordered_map<const DfgVertexVar*, std::unordered_map<uint64_t, DfgVertexVar*>> m_clones;
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// METHODS
|
2025-08-05 14:03:30 +02:00
|
|
|
void visitMergeSCCs(DfgVertex& vtx, uint64_t targetComponent) {
|
|
|
|
|
VertexState vtxState{vtx};
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Move on if already visited
|
2025-08-05 14:03:30 +02:00
|
|
|
if (vtxState.merged()) return;
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Visiting vertex
|
2025-08-05 14:03:30 +02:00
|
|
|
vtxState.setMerged();
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Assign vertex to the target component
|
2025-08-05 14:03:30 +02:00
|
|
|
vtxState.component(targetComponent);
|
2022-10-08 15:34:07 +02:00
|
|
|
|
2022-12-23 17:32:38 +01:00
|
|
|
// Visit all neighbors. We stop at variable boundaries,
|
2022-10-08 15:34:07 +02:00
|
|
|
// which is where we will split the graphs
|
2023-11-06 13:13:31 +01:00
|
|
|
vtx.forEachSource([this, targetComponent](DfgVertex& other) {
|
2022-10-08 15:34:07 +02:00
|
|
|
if (other.is<DfgVertexVar>()) return;
|
|
|
|
|
visitMergeSCCs(other, targetComponent);
|
|
|
|
|
});
|
2023-11-06 13:13:31 +01:00
|
|
|
vtx.forEachSink([this, targetComponent](DfgVertex& other) {
|
2022-10-08 15:34:07 +02:00
|
|
|
if (other.is<DfgVertexVar>()) return;
|
|
|
|
|
visitMergeSCCs(other, targetComponent);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mergeSCCs() {
|
|
|
|
|
// Ensure that component boundaries are always at variables, by merging SCCs. Merging stops
|
|
|
|
|
// at variable boundaries, so we don't need to iterate variables. Constants are reachable
|
2022-12-03 00:46:38 +01:00
|
|
|
// from their sinks, or are unused, so we don't need to iterate them either.
|
2024-03-26 00:06:25 +01:00
|
|
|
for (DfgVertex& vtx : m_dfg.opVertices()) {
|
2022-10-08 15:34:07 +02:00
|
|
|
// Start DFS from each vertex that is in a non-trivial SCC, and merge everything
|
|
|
|
|
// that is reachable from it into this component.
|
2025-08-05 14:03:30 +02:00
|
|
|
if (const uint64_t target = VertexState{vtx}.component()) visitMergeSCCs(vtx, target);
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retrieve clone of vertex in the given component
|
2025-08-05 14:03:30 +02:00
|
|
|
DfgVertexVar& getClone(DfgVertexVar& vtx, uint64_t component) {
|
|
|
|
|
UASSERT_OBJ(VertexState{vtx}.component() != component, &vtx,
|
|
|
|
|
"Vertex is in that component");
|
2022-10-08 15:34:07 +02:00
|
|
|
DfgVertexVar*& clonep = m_clones[&vtx][component];
|
|
|
|
|
if (!clonep) {
|
|
|
|
|
if (DfgVarPacked* const pVtxp = vtx.cast<DfgVarPacked>()) {
|
2025-07-01 23:55:08 +02:00
|
|
|
if (AstVarScope* const vscp = pVtxp->varScopep()) {
|
|
|
|
|
clonep = new DfgVarPacked{m_dfg, vscp};
|
|
|
|
|
} else {
|
|
|
|
|
clonep = new DfgVarPacked{m_dfg, pVtxp->varp()};
|
|
|
|
|
}
|
2022-10-08 15:34:07 +02:00
|
|
|
} else if (DfgVarArray* const aVtxp = vtx.cast<DfgVarArray>()) {
|
2025-07-01 23:55:08 +02:00
|
|
|
if (AstVarScope* const vscp = aVtxp->varScopep()) {
|
|
|
|
|
clonep = new DfgVarArray{m_dfg, vscp};
|
|
|
|
|
} else {
|
|
|
|
|
clonep = new DfgVarArray{m_dfg, aVtxp->varp()};
|
|
|
|
|
}
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type");
|
2025-08-05 14:03:30 +02:00
|
|
|
clonep->setUser<uint64_t>(component);
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
return *clonep;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 23:09:34 +02:00
|
|
|
// Fix edges that cross components
|
|
|
|
|
void fixEdges(DfgVertexVar& vtx) {
|
2025-08-05 14:03:30 +02:00
|
|
|
const uint64_t component = VertexState{vtx}.component();
|
2022-10-08 15:34:07 +02:00
|
|
|
|
2025-07-22 15:03:35 +02:00
|
|
|
// Fix up sources in a different component
|
|
|
|
|
vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) {
|
|
|
|
|
DfgVertex* const srcp = edge.sourcep();
|
|
|
|
|
if (!srcp) return;
|
2025-08-05 14:03:30 +02:00
|
|
|
const uint64_t sourceComponent = VertexState{*srcp}.component();
|
2025-07-22 15:03:35 +02:00
|
|
|
// Same component is OK
|
|
|
|
|
if (sourceComponent == component) return;
|
|
|
|
|
// Relink the source to write the clone
|
|
|
|
|
edge.unlinkSource();
|
|
|
|
|
getClone(vtx, sourceComponent).srcp(srcp);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Fix up sinks in a different component
|
2022-10-08 15:34:07 +02:00
|
|
|
vtx.forEachSinkEdge([&](DfgEdge& edge) {
|
2025-08-05 14:03:30 +02:00
|
|
|
const uint64_t sinkComponent = VertexState{*edge.sinkp()}.component();
|
2022-10-08 15:34:07 +02:00
|
|
|
// Same component is OK
|
|
|
|
|
if (sinkComponent == component) return;
|
|
|
|
|
// Relink the sink to read the clone
|
|
|
|
|
edge.relinkSource(&getClone(vtx, sinkComponent));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-26 00:06:25 +01:00
|
|
|
template <typename Vertex>
|
|
|
|
|
void moveVertices(DfgVertex::List<Vertex>& list) {
|
|
|
|
|
for (DfgVertex* const vtxp : list.unlinkable()) {
|
2022-10-08 15:34:07 +02:00
|
|
|
DfgVertex& vtx = *vtxp;
|
2025-08-05 14:03:30 +02:00
|
|
|
if (const uint64_t component = VertexState{vtx}.component()) {
|
2022-10-08 15:34:07 +02:00
|
|
|
m_dfg.removeVertex(vtx);
|
|
|
|
|
m_components[component - 1]->addVertex(vtx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void checkEdges(DfgGraph& dfg) const {
|
|
|
|
|
// Check that:
|
|
|
|
|
// - Edges only cross components at variable boundaries
|
|
|
|
|
// - Variable vertex sources are all connected.
|
|
|
|
|
dfg.forEachVertex([&](DfgVertex& vtx) {
|
2025-08-05 14:03:30 +02:00
|
|
|
const uint64_t component = VertexState{vtx}.component();
|
2022-10-08 15:34:07 +02:00
|
|
|
vtx.forEachSource([&](DfgVertex& src) {
|
|
|
|
|
if (src.is<DfgVertexVar>()) return; // OK to cross at variables
|
2025-08-05 14:03:30 +02:00
|
|
|
UASSERT_OBJ(component == VertexState{src}.component(), &vtx,
|
2022-10-08 15:34:07 +02:00
|
|
|
"Edge crossing components without variable involvement");
|
|
|
|
|
});
|
|
|
|
|
vtx.forEachSink([&](DfgVertex& snk) {
|
|
|
|
|
if (snk.is<DfgVertexVar>()) return; // OK to cross at variables
|
2025-08-05 14:03:30 +02:00
|
|
|
UASSERT_OBJ(component == VertexState{snk}.component(), &vtx,
|
2022-10-08 15:34:07 +02:00
|
|
|
"Edge crossing components without variable involvement");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void checkGraph(DfgGraph& dfg) const {
|
|
|
|
|
// Build set of vertices
|
|
|
|
|
std::unordered_set<const DfgVertex*> vertices{dfg.size()};
|
|
|
|
|
dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); });
|
|
|
|
|
|
|
|
|
|
// Check that each edge connects to a vertex that is within the same graph
|
|
|
|
|
dfg.forEachVertex([&](DfgVertex& vtx) {
|
|
|
|
|
vtx.forEachSource([&](DfgVertex& src) {
|
|
|
|
|
UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph");
|
|
|
|
|
});
|
|
|
|
|
vtx.forEachSink([&](DfgVertex& snk) {
|
|
|
|
|
UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 14:03:30 +02:00
|
|
|
void extractComponents(uint32_t numNonTrivialSCCs) {
|
2022-10-08 15:34:07 +02:00
|
|
|
// Allocate result graphs
|
2025-08-05 14:03:30 +02:00
|
|
|
m_components.resize(numNonTrivialSCCs);
|
|
|
|
|
for (uint32_t i = 0; i < numNonTrivialSCCs; ++i) {
|
2025-07-01 23:55:08 +02:00
|
|
|
m_components[i].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i)});
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fix up edges crossing components (we can only do this at variable boundaries, and the
|
|
|
|
|
// earlier merging of components ensured crossing in fact only happen at variable
|
|
|
|
|
// boundaries). Note that fixing up the edges can create clones of variables. Clones do
|
|
|
|
|
// not need fixing up, so we do not need to iterate them.
|
2024-03-26 00:06:25 +01:00
|
|
|
DfgVertex* const lastp = m_dfg.varVertices().backp();
|
|
|
|
|
for (DfgVertexVar& vtx : m_dfg.varVertices()) {
|
2022-10-08 15:34:07 +02:00
|
|
|
// Fix up the edges crossing components
|
|
|
|
|
fixEdges(vtx);
|
|
|
|
|
// Don't iterate clones added during this loop
|
2024-03-26 00:06:25 +01:00
|
|
|
if (&vtx == lastp) break;
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check results for consistency
|
|
|
|
|
if (VL_UNLIKELY(m_doExpensiveChecks)) {
|
|
|
|
|
checkEdges(m_dfg);
|
|
|
|
|
for (const auto& dfgp : m_components) checkEdges(*dfgp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Move other vertices to their component graphs
|
|
|
|
|
// After this, vertex states are invalid as we moved the vertices
|
2024-03-26 00:06:25 +01:00
|
|
|
moveVertices(m_dfg.varVertices());
|
|
|
|
|
moveVertices(m_dfg.constVertices());
|
|
|
|
|
moveVertices(m_dfg.opVertices());
|
2022-10-08 15:34:07 +02:00
|
|
|
|
|
|
|
|
// Check results for consistency
|
|
|
|
|
if (VL_UNLIKELY(m_doExpensiveChecks)) {
|
|
|
|
|
checkGraph(m_dfg);
|
|
|
|
|
for (const auto& dfgp : m_components) checkGraph(*dfgp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CONSTRUCTOR - entry point
|
2022-11-27 11:52:40 +01:00
|
|
|
explicit ExtractCyclicComponents(DfgGraph& dfg, const std::string& label)
|
2022-10-08 15:34:07 +02:00
|
|
|
: m_dfg{dfg}
|
|
|
|
|
, m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} {
|
2025-08-05 14:03:30 +02:00
|
|
|
// DfgVertex::user<uint64_t> is set to the SCC number by colorStronglyConnectedComponents,
|
|
|
|
|
// Then we use VertexState to handle the MSB as an extra flag.
|
2022-10-08 15:34:07 +02:00
|
|
|
const auto userDataInUse = dfg.userDataInUse();
|
|
|
|
|
// Find all the non-trivial SCCs (and trivial cycles) in the graph
|
2025-08-05 14:03:30 +02:00
|
|
|
const uint32_t numNonTrivialSCCs = V3DfgPasses::colorStronglyConnectedComponents(dfg);
|
|
|
|
|
// If the graph was acyclic (which should be the common case), then we are done.
|
|
|
|
|
if (!numNonTrivialSCCs) return;
|
2022-10-08 15:34:07 +02:00
|
|
|
// Ensure that component boundaries are always at variables, by merging SCCs
|
|
|
|
|
mergeSCCs();
|
|
|
|
|
// Extract the components
|
2025-08-05 14:03:30 +02:00
|
|
|
extractComponents(numNonTrivialSCCs);
|
2022-10-08 15:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static std::vector<std::unique_ptr<DfgGraph>> apply(DfgGraph& dfg, const std::string& label) {
|
|
|
|
|
return std::move(ExtractCyclicComponents{dfg, label}.m_components);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::vector<std::unique_ptr<DfgGraph>> DfgGraph::extractCyclicComponents(std::string label) {
|
|
|
|
|
return ExtractCyclicComponents::apply(*this, label);
|
|
|
|
|
}
|