mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +02:00
Add -fno-dead-members and optimize dead class member functions. (#8330)
This commit is contained in:
+146
-37
@@ -52,22 +52,28 @@ class DeadVertex final : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(DeadVertex, V3GraphVertex)
|
||||
AstNode* const m_nodep; // Node that created vertex
|
||||
uint64_t m_workPos = 0; // Position on DeadWorkList, 0 = not on list
|
||||
bool m_removable; // Subject to dead removal
|
||||
bool m_removable = false; // Subject to dead removal
|
||||
bool m_isVirtual = false; // Virtual node, indirection
|
||||
|
||||
public:
|
||||
DeadVertex(V3Graph* graphp, AstNode* nodep, bool removable)
|
||||
DeadVertex(V3Graph* graphp, AstNode* nodep)
|
||||
: V3GraphVertex{graphp}
|
||||
, m_nodep{nodep}
|
||||
, m_removable{removable} {}
|
||||
, m_nodep{nodep} {}
|
||||
~DeadVertex() override = default;
|
||||
string dotShape() const override { return removable() ? "rectangle" : "ellipse"; }
|
||||
string dotShape() const override {
|
||||
return isVirtual() ? "diamond" : removable() ? "rectangle" : "ellipse";
|
||||
}
|
||||
AstNode* nodep() const VL_MT_STABLE { return m_nodep; }
|
||||
string name() const override VL_MT_STABLE {
|
||||
return (removable() ? "[R] "s : "") + nodep()->typeName() + ' ' + cvtToHex(nodep()) + ' '
|
||||
+ nodep()->name();
|
||||
return (isVirtual() ? "[VIRT] "
|
||||
: removable() ? "[R] "s
|
||||
: "[NR] ")
|
||||
+ nodep()->typeName() + ' ' + cvtToHex(nodep()) + ' ' + nodep()->name();
|
||||
}
|
||||
bool removable() const { return m_removable; }
|
||||
void removable(bool flag) { m_removable = flag; }
|
||||
bool isVirtual() const { return m_isVirtual; }
|
||||
void isVirtual(bool flag) { m_isVirtual = flag; }
|
||||
uint64_t workPos() const { return m_workPos; }
|
||||
void workPos(uint64_t value) { m_workPos = value; }
|
||||
};
|
||||
@@ -117,7 +123,13 @@ class DeadGraph final : public V3Graph {
|
||||
// See const VNUser2InUse m_inuser2; inside DeadVisitor
|
||||
// MEMBERS
|
||||
DeadWorkList m_funcs; // Functions eligble for deletion
|
||||
// Each virtual vertex by the ftask name()
|
||||
std::unordered_map<std::string, DeadVertex*> m_virtualVtxsp;
|
||||
|
||||
void newEdge(DeadVertex* fromp, DeadVertex* top) {
|
||||
new V3GraphEdge{this, fromp, top, 1, false};
|
||||
if (top->removable()) m_funcs.erase(top); // Now has an input edge
|
||||
}
|
||||
void pushWorkMaybe(DeadVertex* vtxp, bool allowSize1) {
|
||||
if (vtxp->removable()) {
|
||||
if (VN_IS(vtxp->nodep(), NodeFTask)
|
||||
@@ -130,18 +142,32 @@ public:
|
||||
// METHODS
|
||||
DeadGraph() = default;
|
||||
~DeadGraph() override = default;
|
||||
DeadVertex* findNewVertex(AstNode* nodep, bool removable) {
|
||||
DeadVertex* findNewVertex(AstNode* nodep) {
|
||||
DeadVertex* vtxp = nodep->user2u().to<DeadVertex*>();
|
||||
if (!vtxp) {
|
||||
vtxp = new DeadVertex{this, nodep, removable};
|
||||
vtxp = new DeadVertex{this, nodep};
|
||||
nodep->user2p(vtxp);
|
||||
pushWorkMaybe(vtxp, false);
|
||||
}
|
||||
UASSERT_OBJ(vtxp->nodep() == nodep, nodep, "Vertex points at different node");
|
||||
return vtxp;
|
||||
}
|
||||
DeadVertex* findVirtualVertex(AstNode* nodep) {
|
||||
const auto it = m_virtualVtxsp.find(nodep->name());
|
||||
DeadVertex* vtxp;
|
||||
if (it != m_virtualVtxsp.end()) {
|
||||
vtxp = it->second;
|
||||
} else {
|
||||
vtxp = new DeadVertex{this, nodep};
|
||||
vtxp->removable(true);
|
||||
vtxp->isVirtual(true);
|
||||
pushWorkMaybe(vtxp, false);
|
||||
m_virtualVtxsp.emplace(nodep->name(), vtxp);
|
||||
}
|
||||
return vtxp;
|
||||
}
|
||||
void findNewRemovableVertex(AstNode* nodep, bool removable) {
|
||||
DeadVertex* const vtxp = findNewVertex(nodep, removable);
|
||||
DeadVertex* const vtxp = findNewVertex(nodep);
|
||||
// Wasn't removable before (due to earlier insert), make removable now
|
||||
if (removable && !vtxp->removable()) {
|
||||
vtxp->removable(true);
|
||||
@@ -149,35 +175,65 @@ public:
|
||||
}
|
||||
}
|
||||
void deleteNodeVertex(AstNode* nodep) {
|
||||
if (DeadVertex* const vtxp = nodep->user2u().to<DeadVertex*>()) {
|
||||
UINFO(9, "Delete vertex due to node deletion " << vtxp->name());
|
||||
// Mark all about-to-empty downstream vertices onto worklist
|
||||
for (const V3GraphEdge& oedge : vtxp->outEdges()) {
|
||||
DeadVertex* const toVtxp = static_cast<DeadVertex*>(oedge.top());
|
||||
pushWorkMaybe(toVtxp, true);
|
||||
}
|
||||
nodep->user2p(nullptr); // Shouldn't be checked later as deleting, but in case
|
||||
if (vtxp->removable()) m_funcs.erase(vtxp);
|
||||
VL_DO_DANGLING(vtxp->unlinkDelete(this), vtxp);
|
||||
if (DeadVertex* const vtxp = nodep->user2u().to<DeadVertex*>()) deleteVertex(vtxp);
|
||||
}
|
||||
void deleteVertex(DeadVertex* vtxp) {
|
||||
UINFO(9, "Delete vertex " << vtxp->name());
|
||||
// Mark all about-to-empty downstream vertices onto worklist
|
||||
for (const V3GraphEdge& oedge : vtxp->outEdges()) {
|
||||
DeadVertex* const toVtxp = static_cast<DeadVertex*>(oedge.top());
|
||||
pushWorkMaybe(toVtxp, true); // size1 ok as about to delete below
|
||||
}
|
||||
// Node shouldn't be looking at user2p later as node being deleting, but in case
|
||||
if (!vtxp->isVirtual()) vtxp->nodep()->user2p(nullptr);
|
||||
if (vtxp->removable()) m_funcs.erase(vtxp);
|
||||
VL_DO_DANGLING(vtxp->unlinkDelete(this), vtxp);
|
||||
}
|
||||
// This only tracks usage dependancy, not "containership",
|
||||
// When all needs disappear the related node is eligble for deletion
|
||||
void needs(AstNode* nodep, AstNode* parentp) {
|
||||
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
|
||||
UINFO(9, "Edge node " << nodep << " -> " << parentp);
|
||||
DeadVertex* const parentVtxp = findNewVertex(parentp, false);
|
||||
DeadVertex* const nodeVtxp = findNewVertex(nodep, false);
|
||||
DeadVertex* const parentVtxp = findNewVertex(parentp);
|
||||
DeadVertex* const nodeVtxp = findNewVertex(nodep);
|
||||
UINFO(9, "Edge need " << parentVtxp << " -> " << nodeVtxp);
|
||||
new V3GraphEdge{this, parentVtxp, nodeVtxp, 1, false};
|
||||
if (nodeVtxp->removable()) m_funcs.erase(nodeVtxp); // Now has an input edge
|
||||
newEdge(parentVtxp, nodeVtxp);
|
||||
}
|
||||
void needsVirtual(AstNodeFTask* nodep, AstNode* parentp) {
|
||||
// Virtual call can be to any function in the call hierarchy.
|
||||
// For simplicity rather than tracking possible multiple base
|
||||
// classes (due to 'implements' classes there can be more than
|
||||
// one), we simply assume all virtual functions of the same name
|
||||
// can call any other virtual function of the same name
|
||||
// Track via an intermediate node.
|
||||
// All calling parents' verticies -> Virtual Vertex -> all ftasks verticies
|
||||
if (parentp == nodep) return; // No need for tracking needs itself (recursion)
|
||||
DeadVertex* const parentVtxp = findNewVertex(parentp);
|
||||
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
|
||||
UINFO(9, "Edge needVirtual " << parentVtxp << " -> " << virtualVtxp);
|
||||
newEdge(parentVtxp, virtualVtxp);
|
||||
}
|
||||
void funcVirtual(AstNodeFTask* nodep) {
|
||||
// Virtual Vertex -> all ftasks verticies
|
||||
DeadVertex* const virtualVtxp = findVirtualVertex(nodep);
|
||||
DeadVertex* const nodeVtxp = findNewVertex(nodep);
|
||||
UINFO(9, "Edge funcvirtual " << virtualVtxp << " -> " << nodeVtxp);
|
||||
newEdge(virtualVtxp, nodeVtxp);
|
||||
}
|
||||
bool funcsEmpty() const { return m_funcs.empty(); }
|
||||
AstNode* funcsGetPopFront() {
|
||||
DeadVertex* const nodeVtxp = m_funcs.getPopFront();
|
||||
AstNode* const nodep = nodeVtxp->nodep();
|
||||
UASSERT_OBJ(nodeVtxp->inEmpty(), nodep, "Non-empty node on work list");
|
||||
UASSERT_OBJ(nodeVtxp->removable(), nodep, "Non-removable node on work list");
|
||||
DeadVertex* const vtxp = m_funcs.getPopFront();
|
||||
UASSERT_OBJ(vtxp->inEmpty(), vtxp->nodep(), "Non-empty node on work list");
|
||||
UASSERT_OBJ(vtxp->removable(), vtxp->nodep(), "Non-removable node on work list");
|
||||
if (vtxp->isVirtual()) {
|
||||
// Emptied (no inbound edge) virtual wrapper; all
|
||||
// destinations verticies are now unused too (e.g. all virtual
|
||||
// functions of this name may be deleted)
|
||||
UINFO(9, "Removing virtual " << vtxp);
|
||||
deleteVertex(vtxp);
|
||||
return nullptr; // Caller will search again
|
||||
}
|
||||
AstNode* const nodep = vtxp->nodep();
|
||||
return nodep;
|
||||
}
|
||||
};
|
||||
@@ -225,7 +281,10 @@ class DeadVisitor final : public VNVisitor {
|
||||
AstNode* m_containingFTaskRefp = nullptr; // Parent of ftaskref (e.g. task/module)
|
||||
|
||||
// STATE - Statistic tracking
|
||||
VDouble0 m_statFTasksDeadified;
|
||||
VDouble0 m_statFTasksDemoted;
|
||||
VDouble0 m_statFTasksMDeadified;
|
||||
VDouble0 m_statFTasksNMDeadified;
|
||||
VDouble0 m_statFTasksVirtDeadified;
|
||||
|
||||
// METHODS
|
||||
|
||||
@@ -260,6 +319,14 @@ class DeadVisitor final : public VNVisitor {
|
||||
if (AstNode* const subnodep = nodep->virtRefDTypep()) subnodep->user1Inc();
|
||||
if (AstNode* const subnodep = nodep->virtRefDType2p()) subnodep->user1Inc();
|
||||
}
|
||||
void needsTask(AstNodeFTask* taskp, AstNode* containerp) {
|
||||
if (!taskp) return; // Unlinked
|
||||
if (taskp->isVirtual()) {
|
||||
m_graph.needsVirtual(taskp, containerp);
|
||||
} else {
|
||||
m_graph.needs(taskp, containerp);
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
@@ -319,7 +386,7 @@ class DeadVisitor final : public VNVisitor {
|
||||
iterateChildren(nodep);
|
||||
if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true;
|
||||
checkAll(nodep);
|
||||
if (nodep->taskp()) m_graph.needs(nodep->taskp(), m_containingFTaskRefp);
|
||||
needsTask(nodep->taskp(), m_containingFTaskRefp);
|
||||
if (nodep->classOrPackagep()) {
|
||||
if (m_elimCells) {
|
||||
nodep->classOrPackagep(nullptr);
|
||||
@@ -331,7 +398,7 @@ class DeadVisitor final : public VNVisitor {
|
||||
void visit(AstModportFTaskRef* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
checkAll(nodep);
|
||||
if (nodep->ftaskp()) m_graph.needs(nodep->ftaskp(), m_containingFTaskRefp);
|
||||
needsTask(nodep->ftaskp(), m_containingFTaskRefp);
|
||||
}
|
||||
void visit(AstRefDType* nodep) override {
|
||||
iterateChildren(nodep);
|
||||
@@ -472,13 +539,15 @@ class DeadVisitor final : public VNVisitor {
|
||||
}
|
||||
void visit(AstNodeFTask* nodep) override {
|
||||
const bool removable = !(nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport()
|
||||
|| nodep->classMethod());
|
||||
|| nodep->keepAlive() || nodep->isConstructor()
|
||||
|| (!v3Global.opt.fDeadMethods() && nodep->classMethod()));
|
||||
m_graph.findNewRemovableVertex(nodep, removable);
|
||||
//
|
||||
VL_RESTORER(m_containingFTaskRefp);
|
||||
m_containingFTaskRefp = nodep;
|
||||
iterateChildren(nodep);
|
||||
checkAll(nodep);
|
||||
if (nodep->isVirtual()) m_graph.funcVirtual(nodep);
|
||||
if (!removable) {
|
||||
if (m_modp && !m_modp->dead() && !m_modp->verilatorLib())
|
||||
m_modp->user1Inc(); // Keep container
|
||||
@@ -523,10 +592,41 @@ class DeadVisitor final : public VNVisitor {
|
||||
|
||||
void deadCheckTasks() {
|
||||
while (!m_graph.funcsEmpty()) {
|
||||
AstNode* const taskp = m_graph.funcsGetPopFront();
|
||||
UINFO(9, "Dead task " << taskp);
|
||||
deleting(taskp);
|
||||
++m_statFTasksDeadified;
|
||||
AstNode* const nodep = m_graph.funcsGetPopFront();
|
||||
if (!nodep) continue;
|
||||
UINFO(9, "Dead " << nodep);
|
||||
if (AstNodeFTask* const taskp = VN_CAST(nodep, NodeFTask)) {
|
||||
if (taskp->isVirtual()) {
|
||||
++m_statFTasksVirtDeadified;
|
||||
} else if (taskp->classMethod()) {
|
||||
++m_statFTasksMDeadified;
|
||||
} else {
|
||||
++m_statFTasksNMDeadified;
|
||||
}
|
||||
}
|
||||
deleting(nodep);
|
||||
}
|
||||
}
|
||||
|
||||
void deadCheckDemote() {
|
||||
for (V3GraphVertex& gvtx : m_graph.vertices()) {
|
||||
DeadVertex* const vtxp = gvtx.cast<DeadVertex>();
|
||||
// A isVirtual vertex with single out means there's only one target virtual function
|
||||
// that can be virtually called, so can make it non-virtual for faster execution
|
||||
// (UVM benefits from this)
|
||||
if (!vtxp->outSize1()) continue;
|
||||
if (!vtxp->isVirtual()) continue;
|
||||
for (V3GraphEdge& edge : vtxp->outEdges()) { // Always a single one
|
||||
AstNode* const nodep = edge.top()->as<DeadVertex>()->nodep();
|
||||
AstNodeFTask* const funcp = VN_AS(nodep, NodeFTask);
|
||||
UASSERT_OBJ(funcp->isVirtual(), funcp,
|
||||
"Only virtual ftasks should be under DeadVirtualVertex");
|
||||
if (v3Global.opt.fDeadMethods()) {
|
||||
funcp->isVirtual(false);
|
||||
UINFO(9, "Demote to non-virtual " << funcp);
|
||||
++m_statFTasksDemoted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,13 +846,22 @@ public:
|
||||
if (!elimTopIfaces) preserveTopIfaces(nodep);
|
||||
deadCheckMod();
|
||||
|
||||
// After deleting as much as can, demote some virtual functions
|
||||
if (elimTasks) deadCheckDemote();
|
||||
|
||||
// We may have removed some datatypes, cleanup
|
||||
nodep->typeTablep()->repairCache();
|
||||
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
|
||||
nodep->constPoolp()->rebuildVarScopesAndCache();
|
||||
}
|
||||
~DeadVisitor() override {
|
||||
V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified);
|
||||
V3Stats::addStatSum("Optimizations, FTasks, virtual-to-nonvirtual demotion",
|
||||
m_statFTasksDemoted);
|
||||
V3Stats::addStatSum("Optimizations, FTasks, deadified, methods", m_statFTasksMDeadified);
|
||||
V3Stats::addStatSum("Optimizations, FTasks, deadified, non-methods",
|
||||
m_statFTasksNMDeadified);
|
||||
V3Stats::addStatSum("Optimizations, FTasks, deadified, virtual",
|
||||
m_statFTasksVirtDeadified);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user