// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Dead code elimination // // Code available from: https://verilator.org // //************************************************************************* // // 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-FileCopyrightText: 2003-2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // DEAD TRANSFORMATIONS: // Remove any unreferenced modules // Remove any unreferenced variables // // TODO: A graph would make the process of circular and interlinked // dependencies easier to resolve. // NOTE: If redo this, consider using maybePointedTo()/broken() ish scheme // instead of needing as many visitors. // // The following nodes have package pointers and are cleaned up here: // AstRefDType, AstEnumItemRef, AstNodeVarRef, AstNodeFTask // These have packagep but will not exist at this stage // AstPackageImport, AstDot, AstClassOrPackageRef // // Note on packagep: After the V3Scope/V3LinkDotScoped stage, package links // are no longer used, but their presence prevents us from removing empty // packages. As the links as no longer used after V3Scope, we remove them // here after scoping to allow more dead node removal. //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Dead.h" #include "V3Graph.h" #include "V3Stats.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Dead tracking graph 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 = false; // Subject to dead removal bool m_isVirtual = false; // Virtual node, indirection public: DeadVertex(V3Graph* graphp, AstNode* nodep) : V3GraphVertex{graphp} , m_nodep{nodep} {} ~DeadVertex() override = default; 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 (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; } }; // Work list which keeps ordering of elements (so stable), adds always to // the end of the queue, and allows arbitrary removal class DeadWorkList final { // MEMBERS static uint64_t s_sequence; // Sequence number, inc each push std::map m_works; // Work list by sequence public: // METHODS bool empty() const { return m_works.empty(); } void push(DeadVertex* vtxp) { if (vtxp->workPos()) return; // Already on list const uint64_t id = ++s_sequence; vtxp->workPos(id); m_works.emplace(id, vtxp); UINFO(9, "Worklist.push " << vtxp); } void erase(DeadVertex* vtxp) { const uint64_t id = vtxp->workPos(); if (!id) return; UINFO(9, "Worklist.erase " << vtxp); const auto it = m_works.find(id); UASSERT_OBJ(it != m_works.end(), vtxp->nodep(), "vertex thought to be on work list but not"); m_works.erase(it); vtxp->workPos(0); } DeadVertex* getPopFront() { UDEBUGONLY(UASSERT(!empty(), "Front invalid to call on empty list");); const auto it = m_works.begin(); DeadVertex* const vtxp = it->second; m_works.erase(it); vtxp->workPos(0); UINFO(9, "Worklist.getPopFront " << vtxp); return vtxp; } }; uint64_t DeadWorkList::s_sequence = 0; class DeadGraph final : public V3Graph { // NODE STATE // AstNodeFTask::user2p() -> DeadVertex* for this node // 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 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) && (vtxp->inEmpty() || (allowSize1 && vtxp->inSize1()))) m_funcs.push(vtxp); } } public: // METHODS DeadGraph() = default; ~DeadGraph() override = default; DeadVertex* findNewVertex(AstNode* nodep) { DeadVertex* vtxp = nodep->user2u().to(); if (!vtxp) { 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); // Wasn't removable before (due to earlier insert), make removable now if (removable && !vtxp->removable()) { vtxp->removable(true); pushWorkMaybe(vtxp, false); } } void deleteNodeVertex(AstNode* nodep) { if (DeadVertex* const vtxp = nodep->user2u().to()) 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(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); DeadVertex* const nodeVtxp = findNewVertex(nodep); UINFO(9, "Edge need " << parentVtxp << " -> " << nodeVtxp); 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 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; } }; //###################################################################### // Dead state, as a visitor of each AstNode class DeadVisitor final : public VNVisitor { // NODE STATE // Entire Netlist: // AstNodeModule::user1() -> int. Count of number of cells referencing this module. // AstVar::user1() -> int. Count of number of references // AstVar::user2() -> bool. Is a formal function argument // AstVarScope::user1() -> int. Count of number of references // AstNodeDType::user1() -> int. Count of number of references const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; // For usage information see DeadGraph // TYPES using AssignMap = std::multimap; // STATE - across all visitors const bool m_elimUserVars; // Allow removal of user's vars const bool m_elimDTypes; // Allow removal of DTypes const bool m_elimCells; // Allow removal of Cells DeadGraph m_graph; // Tracking graph // List of all encountered to avoid another loop through tree std::vector m_varsp; std::vector m_dtypeElimsp; // Data types might eliminate std::map m_dtypePkgsp; // Data type's containing package std::vector m_vscsp; std::vector m_scopesp; std::vector m_cellsp; std::vector m_classesp; std::vector m_typedefsp; AssignMap m_assignMap; // List of all simple assignments for each variable bool m_sideEffect = false; // Side effects discovered in assign RHS // STATE - for current visit position (use VL_RESTORER) bool m_inAssign = false; // Currently in an assign AstNodeDType* m_curDTypep = nullptr; // Current NodeDType AstNodeModule* m_modp = nullptr; // Current module AstForeachHeader* m_foreachHeaderp = nullptr; // Current foreach header AstNode* m_containingFTaskRefp = nullptr; // Parent of ftaskref (e.g. task/module) bool m_inFTask = false; // Under an AstNodeFTask // STATE - Statistic tracking VDouble0 m_statFTasksDemoted; VDouble0 m_statFTasksMDeadified; VDouble0 m_statFTasksNMDeadified; VDouble0 m_statFTasksVirtDeadified; // METHODS void deleting(AstNode* nodep) { UINFO(9, " deleting " << nodep); m_graph.deleteNodeVertex(nodep); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } // cppcheck-suppress constParameterPointer void checkAll(AstNode* nodep) { if (AstNode* const subnodep = nodep->dtypep()) { if (nodep != subnodep // Not NodeDTypes reference themselves && m_curDTypep != subnodep) { // Not EnumItem referencing parent Enum subnodep->user1Inc(); } } if (AstNode* const subnodep = nodep->getChildDTypep()) subnodep->user1Inc(); } void checkVarRef(AstNodeVarRef* nodep) const { if (nodep->classOrPackagep() && m_elimCells) nodep->classOrPackagep(nullptr); } void checkDType(AstNodeDType* nodep) { if (!nodep->generic() // Don't remove generic types && m_elimDTypes // dtypes stick around until post-widthing && !VN_IS(nodep, MemberDType) // Keep member names iff upper type exists && !nodep->undead() // VoidDType or something Netlist points to ) { m_dtypeElimsp.push_back(nodep); } if (VN_IS(m_modp, Package) || VN_IS(m_modp, Class)) m_dtypePkgsp.emplace(nodep, m_modp); 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 { if (m_modp) m_modp->user1Inc(); // e.g. Class under Package VL_RESTORER(m_modp); m_modp = nodep; VL_RESTORER(m_containingFTaskRefp); m_containingFTaskRefp = nodep; if (nodep->dead()) return; if (nodep->modPublic()) m_modp->user1Inc(); iterateChildren(nodep); checkAll(nodep); if (AstClass* const classp = VN_CAST(nodep, Class)) { if (classp->extendsp()) classp->extendsp()->user1Inc(); if (classp->classOrPackagep()) classp->classOrPackagep()->user1Inc(); m_classesp.push_back(classp); // TODO we don't reclaim dead classes yet - graph implementation instead? classp->user1Inc(); } } void visit(AstCFunc* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->scopep()) nodep->scopep()->user1Inc(); // Keep formal arguments for (AstVar* varp = nodep->argsp(); varp; varp = VN_AS(varp->nextp(), Var)) { varp->user1Inc(); varp->user2(1); } } void visit(AstPin* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->modVarp()) nodep->modVarp()->user1Inc(); } void visit(AstScope* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->aboveScopep()) nodep->aboveScopep()->user1Inc(); // Class packages might have no children, but need to remain as // long as the class they refer to is needed if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc(); if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) { m_scopesp.push_back(nodep); } } void visit(AstCell* nodep) override { iterateChildren(nodep); checkAll(nodep); m_cellsp.push_back(nodep); nodep->modp()->user1Inc(); } void visit(AstNodeVarRef* nodep) override { // Note NodeAssign skips calling this in some cases iterateChildren(nodep); checkAll(nodep); checkVarRef(nodep); if (nodep->varScopep()) { nodep->varScopep()->user1Inc(); nodep->varScopep()->varp()->user1Inc(); } if (nodep->varp()) nodep->varp()->user1Inc(); if (nodep->classOrPackagep()) nodep->classOrPackagep()->user1Inc(); } void visit(AstNodeFTaskRef* nodep) override { iterateChildren(nodep); if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true; checkAll(nodep); needsTask(nodep->taskp(), m_containingFTaskRefp); if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); } else { nodep->classOrPackagep()->user1Inc(); } } } void visit(AstModportFTaskRef* nodep) override { iterateChildren(nodep); checkAll(nodep); needsTask(nodep->ftaskp(), m_containingFTaskRefp); } void visit(AstModportVarRef* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->varp()) nodep->varp()->user1Inc(); } void visit(AstRefDType* nodep) override { iterateChildren(nodep); checkDType(nodep); checkAll(nodep); UASSERT_OBJ(!(m_elimCells && nodep->typedefp()), nodep, "RefDType should point to data type before typedefs removed"); if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); } else { nodep->classOrPackagep()->user1Inc(); } } } void visit(AstClassRefDType* nodep) override { iterateChildren(nodep); checkDType(nodep); checkAll(nodep); if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); } else { nodep->classOrPackagep()->user1Inc(); } } if (nodep->classp()) nodep->classp()->user1Inc(); } void visit(AstIfaceRefDType* nodep) override { iterateChildren(nodep); checkDType(nodep); checkAll(nodep); if (nodep->modportp()) { if (m_elimCells) { nodep->modportp(nullptr); } else { nodep->modportp()->user1Inc(); } } if (nodep->ifaceViaCellp()) nodep->ifaceViaCellp()->user1Inc(); } void visit(AstNodeDType* nodep) override { VL_RESTORER(m_curDTypep); m_curDTypep = nodep; iterateChildren(nodep); checkDType(nodep); checkAll(nodep); } void visit(AstEnumDType* nodep) override { // Widthing during parameter evaluation may have populated the cache. nodep->tableMap().clear(); visit(static_cast(nodep)); } void visit(AstEnumItemRef* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); } else { nodep->classOrPackagep()->user1Inc(); } } checkAll(nodep); } void visit(AstMemberSel* nodep) override { iterateChildren(nodep); if (nodep->varp()) nodep->varp()->user1Inc(); if (nodep->fromp()->dtypep()) nodep->fromp()->dtypep()->user1Inc(); // classref checkAll(nodep); } void visit(AstStructSel* nodep) override { iterateChildren(nodep); if (nodep->fromp()->dtypep()) nodep->fromp()->dtypep()->user1Inc(); // structdtype checkAll(nodep); } void visit(AstModport* nodep) override { iterateChildren(nodep); if (m_elimCells) { if (!nodep->varsp()) { deleting(nodep); return; } } checkAll(nodep); } void visit(AstForeachHeader* nodep) override { // Var under a ForeachHeader means we haven't called V3Width to remove them yet VL_RESTORER(m_foreachHeaderp); m_foreachHeaderp = nodep; iterateChildren(nodep); checkAll(nodep); } void visit(AstTypedef* nodep) override { iterateChildren(nodep); m_typedefsp.push_back(nodep); // Don't let packages with only public variables disappear // Normal modules may disappear, e.g. if they are parameterized then removed if (nodep->attrPublic() && m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc(); } void visit(AstVarScope* nodep) override { iterateChildren(nodep); checkAll(nodep); if (nodep->scopep()) nodep->scopep()->user1Inc(); if (mightElimVar(nodep->varp())) m_vscsp.push_back(nodep); } void visit(AstVar* nodep) override { iterateChildren(nodep); checkAll(nodep); if (m_foreachHeaderp) nodep->user1Inc(); // Keep formal arguments if (m_inFTask && nodep->isIO()) { nodep->user1Inc(); nodep->user2(1); } if (mightElimVar(nodep)) { m_varsp.push_back(nodep); } else { if (m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc(); } } void visit(AstNodeAssign* nodep) override { // See if simple assignments to variables may be eliminated because // that variable is never used. // Similar code in V3Life const bool assignInAssign = m_inAssign; // Might be Assign(..., ExprStmt(Assign), ...) { VL_RESTORER(m_inAssign); VL_RESTORER(m_sideEffect); m_inAssign = true; m_sideEffect = nodep->isTimingControl(); // Can't remove the delay iterateAndNextNull(nodep->rhsp()); checkAll(nodep); // Has to be direct assignment without any EXTRACTing. AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef); if (varrefp && !m_sideEffect && v3Global.opt.fDeadAssigns() && varrefp->varScopep()) { // For simplicity, we only remove post-scoping m_assignMap.emplace(varrefp->varScopep(), nodep); checkAll(varrefp); // Must track reference to dtype() checkVarRef(varrefp); } else { // Track like any other statement iterateAndNextNull(nodep->lhsp()); } iterateNull(nodep->timingControlp()); } if (assignInAssign) m_sideEffect = true; // Parent assign shouldn't optimize } void visit(AstNodeFTask* nodep) override { const bool removable = !(nodep->taskPublic() || nodep->dpiExport() || nodep->dpiImport() || nodep->keepAlive() || nodep->isConstructor() || (!v3Global.opt.fDeadMethods() && nodep->classMethod())); m_graph.findNewRemovableVertex(nodep, removable); // VL_RESTORER(m_containingFTaskRefp); VL_RESTORER(m_inFTask); m_containingFTaskRefp = nodep; m_inFTask = true; 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 } if (nodep->classOrPackagep()) { if (m_elimCells) { nodep->classOrPackagep(nullptr); } else { nodep->classOrPackagep()->user1Inc(); } } } //----- void visit(AstClockingItem* nodep) override { // Prevent V3Dead from deleting clockvars that are seemingly dead before V3AssertPre. Later // the vars will be moved to the containing module so if they are actually dead they will // still get deleted. } void visit(AstNode* nodep) override { if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true; iterateChildren(nodep); checkAll(nodep); } // METHODS void deadCheckTypedefs() { for (AstTypedef* typedefp : m_typedefsp) { if (shouldDeleteTypedef(typedefp)) { deleting(typedefp); continue; } checkAll(typedefp); } } bool shouldDeleteTypedef(const AstTypedef* typedefp) const { if (const auto* const structp = VN_CAST(typedefp->subDTypep(), NodeUOrStructDType)) { if (structp->user1() && !structp->packed()) return false; } return m_elimCells && !typedefp->attrPublic(); } void deadCheckTasks() { while (!m_graph.funcsEmpty()) { 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(); // 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()->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; } } } } void deadCheckMod() { // Kill any unused modules // V3LinkCells has a graph that is capable of this too, but we need to do it // after we've done all the generate blocks for (bool retry = true; retry;) { retry = false; AstNodeModule* nextmodp; for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; modp = nextmodp) { nextmodp = VN_AS(modp->nextp(), NodeModule); // Keep $unit until m_elimCells stages. Note v3Global.opt.serializeOnly() // won't reach this stage, and will always have an empty $unit. That's ok. const bool keep = !m_elimCells && modp == v3Global.rootp()->dollarUnitPkgp(); if (modp->dead() || (!modp->isTop() && modp->user1() == 0 && !keep)) { // > 2 because L1 is the wrapper, L2 is the top user module UINFO(4, " Dead module " << modp); // And its children may now be killable too; correct counts // Recurse, as cells may not be directly under the module but in a generate if (!modp->dead()) { // If was dead didn't increment user1's modp->foreach([](const AstCell* cellp) { // cellp->modp()->user1Inc(-1); }); } if (modp == v3Global.rootp()->dollarUnitPkgp()) { v3Global.rootp()->dollarUnitPkgp(nullptr); } deleting(modp); retry = true; } } } } bool mightElimVar(const AstVar* nodep) const { if (nodep->isSigPublic()) return false; // Can't elim publics! if (nodep->isPrimaryIO() || nodep->isClassMember() || nodep->sensIfacep()) return false; if (nodep->isTemp() && !nodep->isTrace()) return true; return m_elimUserVars; // Post-Trace can kill most anything } void deadCheckScope() { for (bool retry = true; retry;) { retry = false; for (std::vector::iterator it = m_scopesp.begin(); it != m_scopesp.end(); ++it) { AstScope* const scp = *it; if (!scp) continue; if (scp->user1() == 0) { UINFO(4, " Dead AstScope " << scp); scp->aboveScopep()->user1Inc(-1); if (scp->dtypep()) scp->dtypep()->user1Inc(-1); deleting(scp); *it = nullptr; retry = true; } } } } void deadCheckCells() { for (AstCell* cellp : m_cellsp) { if (cellp->user1() == 0 && !cellp->modp()->stmtsp() && v3Global.opt.fDeadCells()) { cellp->modp()->user1Inc(-1); deleting(cellp); } } } void deadCheckClasses() { for (bool retry = true; retry;) { retry = false; for (auto& itr : m_classesp) { if (AstClass* const nodep = itr) { // nullptr if deleted earlier if (nodep->user1() == 0) { if (nodep->extendsp()) nodep->extendsp()->user1Inc(-1); if (nodep->classOrPackagep()) nodep->classOrPackagep()->user1Inc(-1); deleting(nodep); itr = nullptr; retry = true; } } } } } void deadCheckVar() { // Delete any unused varscopes for (AstVarScope* vscp : m_vscsp) { // Keep formal arguments if (vscp->varp()->user2()) continue; if (vscp->user1() == 0) { UINFO(4, " Dead " << vscp); const std::pair eqrange = m_assignMap.equal_range(vscp); for (AssignMap::iterator itr = eqrange.first; itr != eqrange.second; ++itr) { AstNodeAssign* const assp = itr->second; UINFO(4, " Dead assign " << assp); assp->dtypep()->user1Inc(-1); deleting(assp); } if (vscp->scopep()) vscp->scopep()->user1Inc(-1); vscp->dtypep()->user1Inc(-1); deleting(vscp); } } for (bool retry = true; retry;) { retry = false; for (std::vector::iterator it = m_varsp.begin(); it != m_varsp.end(); ++it) { AstVar* const varp = *it; if (!varp) continue; if (varp->user1() == 0) { UINFO(4, " Dead " << varp); if (varp->dtypep()) varp->dtypep()->user1Inc(-1); deleting(varp); *it = nullptr; retry = true; } } } for (std::vector::iterator it = m_dtypeElimsp.begin(); it != m_dtypeElimsp.end(); ++it) { if ((*it)->user1() == 0) { // It's possible that there if a reference to each individual member, but // not to the dtype itself. Check and don't remove the parent dtype if // members are still alive. if (const AstNodeUOrStructDType* const classp = VN_CAST((*it), NodeUOrStructDType)) { bool cont = true; for (AstMemberDType* memberp = classp->membersp(); memberp; memberp = VN_AS(memberp->nextp(), MemberDType)) { if (memberp->user1() != 0) { cont = false; break; } } if (!cont) continue; } deleting(*it); } } } // cppcheck-suppress constParameterPointer void preserveTopIfaces(AstNetlist* rootp) { // cppcheck-suppress constVariablePointer for (AstNodeModule* modp = rootp->modulesp(); modp && modp->isTop(); modp = VN_AS(modp->nextp(), NodeModule)) { for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) { if (AstVar* const varp = VN_CAST(subnodep, Var)) { if (varp->isIfaceRef()) { const AstNodeDType* const subtypep = varp->subDTypep(); const AstIfaceRefDType* ifacerefp = nullptr; if (VN_IS(subtypep, IfaceRefDType)) { ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType); } else if (VN_IS(subtypep, BracketArrayDType)) { const AstBracketArrayDType* const arrp = VN_AS(subtypep, BracketArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); } } else if (VN_IS(subtypep, UnpackArrayDType)) { const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); } } if (ifacerefp && !ifacerefp->cellp() && (ifacerefp->ifacep()->user1() == 0)) { ifacerefp->ifacep()->user1(1); } } } } } } public: // CONSTRUCTORS DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes, bool elimCells, bool elimTopIfaces, bool elimTasks) : m_elimUserVars{elimUserVars} , m_elimDTypes{elimDTypes} , m_elimCells{elimCells} , m_containingFTaskRefp{nodep} { // Prepare to remove some datatypes nodep->typeTablep()->clearCache(); // Operate on whole netlist iterate(nodep); if (AstVarScope* const vscp = nodep->dpiExportTriggerp()) { vscp->user1Inc(); vscp->varp()->user1Inc(); } // If data type has a reference in another package, then keep defining package around for (auto& itr : m_dtypePkgsp) { if (itr.first->user1()) itr.second->user1Inc(); } // Simplify redundant edges (e.g. function calls another function many times) m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); if (dumpGraphLevel() >= 9 || debug() >= 9) m_graph.dumpDotFilePrefixed("dead_graph", false); if (elimTasks) deadCheckTasks(); deadCheckTypedefs(); deadCheckVar(); // We only eliminate scopes when in a flattened structure // Otherwise we have no easy way to know if a scope is used if (elimScopes) deadCheckScope(); if (elimCells) deadCheckCells(); deadCheckClasses(); // Modules after vars, because might be vars we delete inside a mod we delete 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, 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); }; }; //###################################################################### // Dead class functions void V3Dead::deadifyModules(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { // node, elimUserVars, elimDTypes, elimScopes, elimCells, elimTopIfaces DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported(), false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTreeEitherLevel() >= 6); } void V3Dead::deadifyDTypes(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { DeadVisitor{nodep, false, true, false, false, false, true}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTreeEitherLevel() >= 3); } void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { DeadVisitor{nodep, false, true, true, false, false, false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTreeEitherLevel() >= 3); } void V3Dead::deadifyAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { DeadVisitor{nodep, true, true, false, true, false, true}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTreeEitherLevel() >= 3); } void V3Dead::deadifyAllScoped(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { DeadVisitor{nodep, true, true, true, true, false, true}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTreeEitherLevel() >= 3); }