2012-04-12 21:08:20 -04:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
2006-08-26 11:35:28 +00:00
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Dead code elimination
|
|
|
|
|
//
|
2019-11-07 22:33:59 -05:00
|
|
|
// Code available from: https://verilator.org
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2025-01-01 08:30:25 -05:00
|
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
2020-03-21 11:24:24 -04:00
|
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
2009-05-04 17:07:57 -04:00
|
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
|
|
|
// Version 2.0.
|
2020-03-21 11:24:24 -04:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DEAD TRANSFORMATIONS:
|
2019-05-19 16:13:13 -04:00
|
|
|
// Remove any unreferenced modules
|
|
|
|
|
// Remove any unreferenced variables
|
2008-06-09 21:25:10 -04:00
|
|
|
//
|
2012-05-02 20:53:19 -04:00
|
|
|
// TODO: A graph would make the process of circular and interlinked
|
|
|
|
|
// dependencies easier to resolve.
|
2009-11-07 21:05:02 -05:00
|
|
|
// NOTE: If redo this, consider using maybePointedTo()/broken() ish scheme
|
|
|
|
|
// instead of needing as many visitors.
|
2016-05-11 21:44:55 -04:00
|
|
|
//
|
|
|
|
|
// 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
|
2020-07-11 10:29:15 -04:00
|
|
|
// AstPackageImport, AstDot, AstClassOrPackageRef
|
2016-05-11 21:44:55 -04:00
|
|
|
//
|
|
|
|
|
// 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
|
2020-11-24 21:28:04 -05:00
|
|
|
// here after scoping to allow more dead node removal.
|
|
|
|
|
//*************************************************************************
|
2019-10-04 20:17:11 -04:00
|
|
|
|
2023-10-18 02:50:27 +00:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2023-10-18 06:37:46 -04:00
|
|
|
#include "V3Dead.h"
|
|
|
|
|
|
2018-10-14 13:43:24 -04:00
|
|
|
#include <vector>
|
|
|
|
|
|
2022-09-18 20:53:42 +01:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
//######################################################################
|
|
|
|
|
// Dead state, as a visitor of each AstNode
|
|
|
|
|
|
2022-01-02 13:56:40 -05:00
|
|
|
class DeadVisitor final : public VNVisitor {
|
2006-08-26 11:35:28 +00:00
|
|
|
// NODE STATE
|
|
|
|
|
// Entire Netlist:
|
2019-05-19 16:13:13 -04:00
|
|
|
// AstNodeModule::user1() -> int. Count of number of cells referencing this module.
|
|
|
|
|
// AstVar::user1() -> int. Count of number of references
|
|
|
|
|
// AstVarScope::user1() -> int. Count of number of references
|
|
|
|
|
// AstNodeDType::user1() -> int. Count of number of references
|
2022-01-02 13:56:40 -05:00
|
|
|
const VNUser1InUse m_inuser1;
|
2006-08-26 11:35:28 +00:00
|
|
|
|
2006-09-26 17:05:08 +00:00
|
|
|
// TYPES
|
2021-03-12 18:10:45 -05:00
|
|
|
using AssignMap = std::multimap<AstVarScope*, AstNodeAssign*>;
|
2006-09-26 17:05:08 +00:00
|
|
|
|
2023-05-27 11:46:25 -04:00
|
|
|
// 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
|
2020-04-15 07:58:34 -04:00
|
|
|
// List of all encountered to avoid another loop through tree
|
|
|
|
|
std::vector<AstVar*> m_varsp;
|
2025-01-04 10:36:34 -05:00
|
|
|
std::vector<AstNode*> m_dtypeElimsp; // Data types might eliminate
|
|
|
|
|
std::map<AstNodeDType*, AstNodeModule*> m_dtypePkgsp; // Data type's containing package
|
2020-04-15 07:58:34 -04:00
|
|
|
std::vector<AstVarScope*> m_vscsp;
|
|
|
|
|
std::vector<AstScope*> m_scopesp;
|
|
|
|
|
std::vector<AstCell*> m_cellsp;
|
|
|
|
|
std::vector<AstClass*> m_classesp;
|
2022-12-21 01:22:42 +01:00
|
|
|
std::vector<AstTypedef*> m_typedefsp;
|
2020-04-15 07:58:34 -04:00
|
|
|
AssignMap m_assignMap; // List of all simple assignments for each variable
|
2020-11-24 21:28:04 -05:00
|
|
|
bool m_sideEffect = false; // Side effects discovered in assign RHS
|
2006-08-26 11:35:28 +00:00
|
|
|
|
2023-05-27 11:46:25 -04:00
|
|
|
// STATE - for current visit position (use VL_RESTORER)
|
2023-05-27 12:43:40 -04:00
|
|
|
bool m_inAssign = false; // Currently in an assign
|
2025-01-04 10:36:34 -05:00
|
|
|
AstNodeDType* m_curDTypep = nullptr; // Current NodeDType
|
2023-05-27 11:46:25 -04:00
|
|
|
AstNodeModule* m_modp = nullptr; // Current module
|
|
|
|
|
AstSelLoopVars* m_selloopvarsp = nullptr; // Current loop vars
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
// METHODS
|
2012-04-29 10:14:13 -04:00
|
|
|
|
2023-11-12 22:08:08 -05:00
|
|
|
void deleting(AstNode* nodep) {
|
|
|
|
|
UINFO(9, " deleting " << nodep << endl);
|
|
|
|
|
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
|
|
|
}
|
|
|
|
|
|
2012-03-03 12:10:29 -05:00
|
|
|
void checkAll(AstNode* nodep) {
|
2025-01-04 10:36:34 -05:00
|
|
|
if (AstNode* const subnodep = nodep->dtypep()) {
|
|
|
|
|
if (nodep != subnodep // Not NodeDTypes reference themselves
|
|
|
|
|
&& m_curDTypep != subnodep) { // Not EnumItem referencing parent Enum
|
|
|
|
|
subnodep->user1Inc();
|
|
|
|
|
}
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2021-11-13 13:50:44 -05:00
|
|
|
if (AstNode* const subnodep = nodep->getChildDTypep()) subnodep->user1Inc();
|
2012-04-29 10:14:13 -04:00
|
|
|
}
|
2022-07-30 11:52:35 -04:00
|
|
|
void checkVarRef(AstNodeVarRef* nodep) const {
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep() && m_elimCells) nodep->classOrPackagep(nullptr);
|
2020-11-24 21:34:11 -05:00
|
|
|
}
|
2012-04-29 10:14:13 -04:00
|
|
|
void checkDType(AstNodeDType* nodep) {
|
2019-05-19 16:13:13 -04:00
|
|
|
if (!nodep->generic() // Don't remove generic types
|
|
|
|
|
&& m_elimDTypes // dtypes stick around until post-widthing
|
2018-02-01 21:32:58 -05:00
|
|
|
&& !VN_IS(nodep, MemberDType) // Keep member names iff upper type exists
|
2021-12-12 18:10:52 -05:00
|
|
|
&& !nodep->undead() // VoidDType or something Netlist points to
|
2020-04-15 07:58:34 -04:00
|
|
|
) {
|
2025-01-04 10:36:34 -05:00
|
|
|
m_dtypeElimsp.push_back(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2025-01-04 10:36:34 -05:00
|
|
|
if (VN_IS(m_modp, Package) || VN_IS(m_modp, Class)) m_dtypePkgsp.emplace(nodep, m_modp);
|
2021-11-13 13:50:44 -05:00
|
|
|
if (AstNode* const subnodep = nodep->virtRefDTypep()) subnodep->user1Inc();
|
|
|
|
|
if (AstNode* const subnodep = nodep->virtRefDType2p()) subnodep->user1Inc();
|
2012-03-03 12:10:29 -05:00
|
|
|
}
|
2006-08-26 11:35:28 +00:00
|
|
|
|
|
|
|
|
// VISITORS
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeModule* nodep) override {
|
2020-11-24 22:46:02 -05:00
|
|
|
if (m_modp) m_modp->user1Inc(); // e.g. Class under Package
|
2020-08-24 21:10:43 -04:00
|
|
|
VL_RESTORER(m_modp);
|
2023-05-27 11:46:25 -04:00
|
|
|
m_modp = nodep;
|
2024-11-25 21:21:11 -05:00
|
|
|
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();
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2014-11-06 17:53:01 -05:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstCFunc* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
|
|
|
|
if (nodep->scopep()) nodep->scopep()->user1Inc();
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstScope* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
|
|
|
|
if (nodep->aboveScopep()) nodep->aboveScopep()->user1Inc();
|
2020-04-05 09:30:23 -04:00
|
|
|
// 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();
|
2022-05-15 16:03:32 +01:00
|
|
|
if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) {
|
2019-05-19 16:13:13 -04:00
|
|
|
m_scopesp.push_back(nodep);
|
|
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstCell* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
|
|
|
|
m_cellsp.push_back(nodep);
|
|
|
|
|
nodep->modp()->user1Inc();
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeVarRef* nodep) override {
|
2020-11-24 21:34:11 -05:00
|
|
|
// Note NodeAssign skips calling this in some cases
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
2020-11-24 21:34:11 -05:00
|
|
|
checkVarRef(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
if (nodep->varScopep()) {
|
|
|
|
|
nodep->varScopep()->user1Inc();
|
|
|
|
|
nodep->varScopep()->varp()->user1Inc();
|
|
|
|
|
}
|
2020-04-15 07:58:34 -04:00
|
|
|
if (nodep->varp()) nodep->varp()->user1Inc();
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) nodep->classOrPackagep()->user1Inc();
|
2009-11-07 21:05:02 -05:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeFTaskRef* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) {
|
2020-04-15 07:58:34 -04:00
|
|
|
if (m_elimCells) {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep(nullptr);
|
2020-04-15 07:58:34 -04:00
|
|
|
} else {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep()->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
}
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2009-11-07 21:05:02 -05:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstMethodCall* nodep) override {
|
2020-03-07 12:52:11 -05:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
checkAll(nodep);
|
|
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstRefDType* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkDType(nodep);
|
|
|
|
|
checkAll(nodep);
|
2020-05-24 14:22:06 -04:00
|
|
|
UASSERT_OBJ(!(m_elimCells && nodep->typedefp()), nodep,
|
|
|
|
|
"RefDType should point to data type before typedefs removed");
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) {
|
2020-04-15 07:58:34 -04:00
|
|
|
if (m_elimCells) {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep(nullptr);
|
2020-04-15 07:58:34 -04:00
|
|
|
} else {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep()->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
}
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstClassRefDType* nodep) override {
|
2020-04-05 09:30:23 -04:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
checkDType(nodep);
|
|
|
|
|
checkAll(nodep);
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) {
|
2020-04-15 07:58:34 -04:00
|
|
|
if (m_elimCells) {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep(nullptr);
|
2020-04-15 07:58:34 -04:00
|
|
|
} else {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep()->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
}
|
2020-04-05 09:30:23 -04:00
|
|
|
}
|
2020-04-15 07:58:34 -04:00
|
|
|
if (nodep->classp()) nodep->classp()->user1Inc();
|
2020-04-05 09:30:23 -04:00
|
|
|
}
|
2022-10-20 12:31:00 +02:00
|
|
|
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();
|
|
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeDType* nodep) override {
|
2025-01-04 10:36:34 -05:00
|
|
|
VL_RESTORER(m_curDTypep);
|
|
|
|
|
m_curDTypep = nodep;
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkDType(nodep);
|
|
|
|
|
checkAll(nodep);
|
2012-04-29 10:14:13 -04:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstEnumItemRef* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) {
|
2020-04-15 07:58:34 -04:00
|
|
|
if (m_elimCells) {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep(nullptr);
|
2020-04-15 07:58:34 -04:00
|
|
|
} else {
|
2020-11-24 21:56:03 -05:00
|
|
|
nodep->classOrPackagep()->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
}
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
checkAll(nodep);
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstMemberSel* nodep) override {
|
2020-03-07 12:52:11 -05:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
if (nodep->varp()) nodep->varp()->user1Inc();
|
|
|
|
|
if (nodep->fromp()->dtypep()) nodep->fromp()->dtypep()->user1Inc(); // classref
|
|
|
|
|
checkAll(nodep);
|
|
|
|
|
}
|
2022-12-21 01:22:42 +01:00
|
|
|
void visit(AstStructSel* nodep) override {
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
if (nodep->fromp()->dtypep()) nodep->fromp()->dtypep()->user1Inc(); // structdtype
|
|
|
|
|
checkAll(nodep);
|
|
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstModport* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
if (m_elimCells) {
|
|
|
|
|
if (!nodep->varsp()) {
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
checkAll(nodep);
|
2011-04-13 19:34:14 -04:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstSelLoopVars* nodep) override {
|
2021-12-11 15:06:33 -05:00
|
|
|
// Var under a SelLoopVars means we haven't called V3Width to remove them yet
|
|
|
|
|
VL_RESTORER(m_selloopvarsp);
|
|
|
|
|
m_selloopvarsp = nodep;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
checkAll(nodep);
|
|
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstTypedef* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2022-12-21 01:22:42 +01:00
|
|
|
m_typedefsp.push_back(nodep);
|
|
|
|
|
|
2019-05-19 16:13:13 -04:00
|
|
|
// Don't let packages with only public variables disappear
|
|
|
|
|
// Normal modules may disappear, e.g. if they are parameterized then removed
|
2018-02-01 21:32:58 -05:00
|
|
|
if (nodep->attrPublic() && m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc();
|
2014-11-07 07:50:11 -05:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstVarScope* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
|
|
|
|
if (nodep->scopep()) nodep->scopep()->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
if (mightElimVar(nodep->varp())) m_vscsp.push_back(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstVar* nodep) override {
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
2018-02-01 21:32:58 -05:00
|
|
|
if (nodep->isSigPublic() && m_modp && VN_IS(m_modp, Package)) m_modp->user1Inc();
|
2021-12-11 15:06:33 -05:00
|
|
|
if (m_selloopvarsp) nodep->user1Inc();
|
2020-04-15 07:58:34 -04:00
|
|
|
if (mightElimVar(nodep)) m_varsp.push_back(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeAssign* nodep) override {
|
2019-05-19 16:13:13 -04:00
|
|
|
// See if simple assignments to variables may be eliminated because
|
|
|
|
|
// that variable is never used.
|
|
|
|
|
// Similar code in V3Life
|
2023-05-27 12:43:40 -04:00
|
|
|
const bool assignInAssign = m_inAssign; // Might be Assign(..., ExprStmt(Assign), ...)
|
2020-11-24 21:34:11 -05:00
|
|
|
{
|
2023-05-27 12:43:40 -04:00
|
|
|
VL_RESTORER(m_inAssign);
|
|
|
|
|
VL_RESTORER(m_sideEffect);
|
|
|
|
|
m_inAssign = true;
|
2020-11-24 21:34:11 -05:00
|
|
|
m_sideEffect = false;
|
|
|
|
|
iterateAndNextNull(nodep->rhsp());
|
|
|
|
|
checkAll(nodep);
|
|
|
|
|
// Has to be direct assignment without any EXTRACTing.
|
2021-11-13 13:50:44 -05:00
|
|
|
AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef);
|
2023-12-22 16:26:51 -05:00
|
|
|
if (varrefp && !m_sideEffect && v3Global.opt.fDeadAssigns()
|
2020-11-24 21:34:11 -05:00
|
|
|
&& varrefp->varScopep()) { // For simplicity, we only remove post-scoping
|
2020-12-18 18:24:47 -05:00
|
|
|
m_assignMap.emplace(varrefp->varScopep(), nodep);
|
2020-11-24 21:34:11 -05:00
|
|
|
checkAll(varrefp); // Must track reference to dtype()
|
|
|
|
|
checkVarRef(varrefp);
|
|
|
|
|
} else { // Track like any other statement
|
|
|
|
|
iterateAndNextNull(nodep->lhsp());
|
|
|
|
|
}
|
2022-08-22 14:26:32 +02:00
|
|
|
iterateNull(nodep->timingControlp());
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2023-05-27 12:43:40 -04:00
|
|
|
if (assignInAssign) m_sideEffect = true; // Parent assign shouldn't optimize
|
2006-09-26 17:05:08 +00:00
|
|
|
}
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
//-----
|
2022-12-23 12:34:49 +00:00
|
|
|
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.
|
|
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNode* nodep) override {
|
2024-07-02 18:46:58 -04:00
|
|
|
if (!m_sideEffect && !nodep->isPure()) m_sideEffect = true;
|
2018-05-10 20:55:37 -04:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
checkAll(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// METHODS
|
2022-12-21 01:22:42 +01:00
|
|
|
void deadCheckTypedefs() {
|
|
|
|
|
for (AstTypedef* typedefp : m_typedefsp) {
|
|
|
|
|
if (shouldDeleteTypedef(typedefp)) {
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(typedefp);
|
2022-12-21 01:22:42 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
checkAll(typedefp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool shouldDeleteTypedef(AstTypedef* typedefp) {
|
2023-01-27 22:41:12 -05:00
|
|
|
if (auto* const structp = VN_CAST(typedefp->subDTypep(), NodeUOrStructDType)) {
|
2022-12-21 01:22:42 +01:00
|
|
|
if (structp->user1() && !structp->packed()) return false;
|
|
|
|
|
}
|
|
|
|
|
return m_elimCells && !typedefp->attrPublic();
|
|
|
|
|
}
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
void deadCheckMod() {
|
2019-05-19 16:13:13 -04:00
|
|
|
// 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
|
2020-04-15 07:58:34 -04:00
|
|
|
for (bool retry = true; retry;) {
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = false;
|
|
|
|
|
AstNodeModule* nextmodp;
|
2020-04-15 07:58:34 -04:00
|
|
|
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; modp = nextmodp) {
|
2021-10-22 13:56:48 +01:00
|
|
|
nextmodp = VN_AS(modp->nextp(), NodeModule);
|
2020-04-15 07:58:34 -04:00
|
|
|
if (modp->dead()
|
|
|
|
|
|| (modp->level() > 2 && modp->user1() == 0 && !modp->internal())) {
|
2019-05-19 16:13:13 -04:00
|
|
|
// > 2 because L1 is the wrapper, L2 is the top user module
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(4, " Dead module " << modp << endl);
|
2019-05-19 16:13:13 -04:00
|
|
|
// 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
|
2022-10-20 20:48:44 +08:00
|
|
|
modp->foreach([](const AstCell* cellp) { //
|
2022-01-09 22:34:10 +00:00
|
|
|
cellp->modp()->user1Inc(-1);
|
|
|
|
|
});
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(modp);
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2022-07-30 11:52:35 -04:00
|
|
|
bool mightElimVar(AstVar* nodep) const {
|
2020-07-02 20:35:55 +09:00
|
|
|
if (nodep->isSigPublic()) return false; // Can't elim publics!
|
2023-12-05 04:11:07 +01:00
|
|
|
if (nodep->isIO() || nodep->isClassMember() || nodep->sensIfacep()) return false;
|
2020-07-02 20:35:55 +09:00
|
|
|
if (nodep->isTemp() && !nodep->isTrace()) return true;
|
|
|
|
|
return m_elimUserVars; // Post-Trace can kill most anything
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
|
|
|
|
void deadCheckScope() {
|
2020-04-15 07:58:34 -04:00
|
|
|
for (bool retry = true; retry;) {
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = false;
|
2020-04-15 07:58:34 -04:00
|
|
|
for (std::vector<AstScope*>::iterator it = m_scopesp.begin(); it != m_scopesp.end();
|
|
|
|
|
++it) {
|
2021-11-13 13:50:44 -05:00
|
|
|
AstScope* const scp = *it;
|
2020-04-15 07:58:34 -04:00
|
|
|
if (!scp) continue;
|
2019-05-19 16:13:13 -04:00
|
|
|
if (scp->user1() == 0) {
|
2018-07-18 21:25:21 -04:00
|
|
|
UINFO(4, " Dead AstScope " << scp << endl);
|
2019-05-19 16:13:13 -04:00
|
|
|
scp->aboveScopep()->user1Inc(-1);
|
2020-04-15 07:58:34 -04:00
|
|
|
if (scp->dtypep()) scp->dtypep()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(scp);
|
2020-08-15 10:12:55 -04:00
|
|
|
*it = nullptr;
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void deadCheckCells() {
|
2020-08-16 11:43:49 -04:00
|
|
|
for (AstCell* cellp : m_cellsp) {
|
2023-12-22 16:26:51 -05:00
|
|
|
if (cellp->user1() == 0 && !cellp->modp()->stmtsp() && v3Global.opt.fDeadCells()) {
|
2019-05-19 16:13:13 -04:00
|
|
|
cellp->modp()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(cellp);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
2020-04-05 09:30:23 -04:00
|
|
|
void deadCheckClasses() {
|
|
|
|
|
for (bool retry = true; retry;) {
|
|
|
|
|
retry = false;
|
2020-11-10 22:10:38 -05:00
|
|
|
for (auto& itr : m_classesp) {
|
2021-11-13 13:50:44 -05:00
|
|
|
if (AstClass* const nodep = itr) { // nullptr if deleted earlier
|
2020-04-05 09:30:23 -04:00
|
|
|
if (nodep->user1() == 0) {
|
|
|
|
|
if (nodep->extendsp()) nodep->extendsp()->user1Inc(-1);
|
2020-11-24 21:56:03 -05:00
|
|
|
if (nodep->classOrPackagep()) nodep->classOrPackagep()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(nodep);
|
2020-11-10 22:10:38 -05:00
|
|
|
itr = nullptr;
|
2020-04-05 09:30:23 -04:00
|
|
|
retry = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
void deadCheckVar() {
|
2019-05-19 16:13:13 -04:00
|
|
|
// Delete any unused varscopes
|
2020-08-16 11:43:49 -04:00
|
|
|
for (AstVarScope* vscp : m_vscsp) {
|
2019-05-19 16:13:13 -04:00
|
|
|
if (vscp->user1() == 0) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(4, " Dead " << vscp << endl);
|
2021-11-26 17:55:36 -05:00
|
|
|
const std::pair<AssignMap::iterator, AssignMap::iterator> eqrange
|
2019-05-19 16:13:13 -04:00
|
|
|
= m_assignMap.equal_range(vscp);
|
|
|
|
|
for (AssignMap::iterator itr = eqrange.first; itr != eqrange.second; ++itr) {
|
2021-11-13 13:50:44 -05:00
|
|
|
AstNodeAssign* const assp = itr->second;
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(4, " Dead assign " << assp << endl);
|
2019-05-19 16:13:13 -04:00
|
|
|
assp->dtypep()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(assp);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
if (vscp->scopep()) vscp->scopep()->user1Inc(-1);
|
|
|
|
|
vscp->dtypep()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(vscp);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 07:58:34 -04:00
|
|
|
for (bool retry = true; retry;) {
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = false;
|
2020-04-15 07:58:34 -04:00
|
|
|
for (std::vector<AstVar*>::iterator it = m_varsp.begin(); it != m_varsp.end(); ++it) {
|
2021-11-13 13:50:44 -05:00
|
|
|
AstVar* const varp = *it;
|
2020-04-15 07:58:34 -04:00
|
|
|
if (!varp) continue;
|
2019-05-19 16:13:13 -04:00
|
|
|
if (varp->user1() == 0) {
|
2018-07-18 21:25:21 -04:00
|
|
|
UINFO(4, " Dead " << varp << endl);
|
2020-04-15 07:58:34 -04:00
|
|
|
if (varp->dtypep()) varp->dtypep()->user1Inc(-1);
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(varp);
|
2020-08-15 10:12:55 -04:00
|
|
|
*it = nullptr;
|
2019-05-19 16:13:13 -04:00
|
|
|
retry = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-04 10:36:34 -05:00
|
|
|
for (std::vector<AstNode*>::iterator it = m_dtypeElimsp.begin(); it != m_dtypeElimsp.end();
|
|
|
|
|
++it) {
|
2019-05-19 16:13:13 -04:00
|
|
|
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.
|
2023-11-12 22:08:08 -05:00
|
|
|
if (const AstNodeUOrStructDType* const classp
|
|
|
|
|
= VN_CAST((*it), NodeUOrStructDType)) {
|
2019-05-19 16:13:13 -04:00
|
|
|
bool cont = true;
|
2020-04-15 07:58:34 -04:00
|
|
|
for (AstMemberDType* memberp = classp->membersp(); memberp;
|
2021-10-22 13:56:48 +01:00
|
|
|
memberp = VN_AS(memberp->nextp(), MemberDType)) {
|
2019-05-19 16:13:13 -04:00
|
|
|
if (memberp->user1() != 0) {
|
|
|
|
|
cont = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 07:58:34 -04:00
|
|
|
if (!cont) continue;
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2023-11-12 22:08:08 -05:00
|
|
|
deleting(*it);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
2022-10-01 09:48:37 -05:00
|
|
|
void preserveTopIfaces(AstNetlist* rootp) {
|
|
|
|
|
for (AstNodeModule* modp = rootp->modulesp(); modp && modp->level() <= 2;
|
2022-10-01 14:53:40 +00:00
|
|
|
modp = VN_AS(modp->nextp(), NodeModule)) {
|
2022-10-01 09:48:37 -05:00
|
|
|
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);
|
2022-10-01 14:53:40 +00:00
|
|
|
} else if (VN_IS(subtypep, BracketArrayDType)) {
|
|
|
|
|
const AstBracketArrayDType* const arrp
|
|
|
|
|
= VN_AS(subtypep, BracketArrayDType);
|
2022-10-01 09:48:37 -05:00
|
|
|
const AstNodeDType* const arrsubtypep = arrp->subDTypep();
|
|
|
|
|
if (VN_IS(arrsubtypep, IfaceRefDType)) {
|
|
|
|
|
ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
|
|
|
|
|
}
|
2022-10-01 14:53:40 +00:00
|
|
|
} else if (VN_IS(subtypep, UnpackArrayDType)) {
|
|
|
|
|
const AstUnpackArrayDType* const arrp
|
|
|
|
|
= VN_AS(subtypep, UnpackArrayDType);
|
2022-10-01 09:48:37 -05:00
|
|
|
const AstNodeDType* const arrsubtypep = arrp->subDTypep();
|
|
|
|
|
if (VN_IS(arrsubtypep, IfaceRefDType)) {
|
|
|
|
|
ifacerefp = VN_AS(arrsubtypep, IfaceRefDType);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-01 14:53:40 +00:00
|
|
|
if (ifacerefp && !ifacerefp->cellp()
|
|
|
|
|
&& (ifacerefp->ifacep()->user1() == 0)) {
|
2022-10-01 09:48:37 -05:00
|
|
|
ifacerefp->ifacep()->user1(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
2020-04-15 07:58:34 -04:00
|
|
|
DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes,
|
2022-10-01 09:48:37 -05:00
|
|
|
bool elimCells, bool elimTopIfaces)
|
2020-11-24 21:28:04 -05:00
|
|
|
: m_elimUserVars{elimUserVars}
|
|
|
|
|
, m_elimDTypes{elimDTypes}
|
|
|
|
|
, m_elimCells{elimCells} {
|
2019-05-19 16:13:13 -04:00
|
|
|
// Prepare to remove some datatypes
|
|
|
|
|
nodep->typeTablep()->clearCache();
|
|
|
|
|
// Operate on whole netlist
|
2018-05-10 20:55:37 -04:00
|
|
|
iterate(nodep);
|
2022-07-14 12:35:44 +01:00
|
|
|
|
|
|
|
|
if (AstVarScope* const vscp = nodep->dpiExportTriggerp()) {
|
|
|
|
|
vscp->user1Inc();
|
|
|
|
|
vscp->varp()->user1Inc();
|
|
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2025-01-04 10:36:34 -05:00
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-21 01:22:42 +01:00
|
|
|
deadCheckTypedefs();
|
2019-05-19 16:13:13 -04:00
|
|
|
deadCheckVar();
|
2019-09-09 07:50:21 -04:00
|
|
|
// We only eliminate scopes when in a flattened structure
|
2019-05-19 16:13:13 -04:00
|
|
|
// Otherwise we have no easy way to know if a scope is used
|
|
|
|
|
if (elimScopes) deadCheckScope();
|
|
|
|
|
if (elimCells) deadCheckCells();
|
2020-04-05 09:30:23 -04:00
|
|
|
deadCheckClasses();
|
2019-05-19 16:13:13 -04:00
|
|
|
// Modules after vars, because might be vars we delete inside a mod we delete
|
2022-10-01 09:48:37 -05:00
|
|
|
if (!elimTopIfaces) preserveTopIfaces(nodep);
|
2019-05-19 16:13:13 -04:00
|
|
|
deadCheckMod();
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2019-05-19 16:13:13 -04:00
|
|
|
// We may have removed some datatypes, cleanup
|
|
|
|
|
nodep->typeTablep()->repairCache();
|
2023-10-09 05:50:31 -04:00
|
|
|
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2022-09-16 11:22:11 +01:00
|
|
|
~DeadVisitor() override = default;
|
2006-08-26 11:35:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Dead class functions
|
|
|
|
|
|
2012-04-29 08:24:32 -04:00
|
|
|
void V3Dead::deadifyModules(AstNetlist* nodep) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2024-11-09 08:24:50 -05:00
|
|
|
{ // node, elimUserVars, elimDTypes, elimScopes, elimCells, elimTopIfaces
|
2022-10-01 14:53:40 +00:00
|
|
|
DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()};
|
|
|
|
|
} // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTreeEitherLevel() >= 6);
|
2012-04-29 08:24:32 -04:00
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2012-04-29 08:24:32 -04:00
|
|
|
void V3Dead::deadifyDTypes(AstNetlist* nodep) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2022-10-01 09:48:37 -05:00
|
|
|
{ DeadVisitor{nodep, false, true, false, false, false}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTreeEitherLevel() >= 3);
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2022-10-01 09:48:37 -05:00
|
|
|
{ DeadVisitor{nodep, false, true, true, false, false}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTreeEitherLevel() >= 3);
|
2012-04-29 08:24:32 -04:00
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
2012-04-29 08:24:32 -04:00
|
|
|
void V3Dead::deadifyAll(AstNetlist* nodep) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2022-10-01 09:48:37 -05:00
|
|
|
{ DeadVisitor{nodep, true, true, false, true, false}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTreeEitherLevel() >= 3);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2016-05-11 21:44:55 -04:00
|
|
|
|
|
|
|
|
void V3Dead::deadifyAllScoped(AstNetlist* nodep) {
|
2020-04-15 07:58:34 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2022-10-01 09:48:37 -05:00
|
|
|
{ DeadVisitor{nodep, true, true, true, true, false}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTreeEitherLevel() >= 3);
|
2016-05-11 21:44:55 -04:00
|
|
|
}
|