// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Add common contents to modules // // 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 // //************************************************************************* // V3Common's Transformations: // // Each class: // Emit to_string() if given class is printed from (used as format arg), or any of its // parents/children is printed from, or if any of its children has another parent that is // printed from. If class emits to_string(), emit to_string() for its struct/union/interface // fields. // Each struct/union: // Emit to_string() if given struct/union is used as format arg or is a field of a // class/struct/union that emits to_string(). If struct/union emits to_string(), emit // to_string() for its struct/union fields. // Each interface: // Emit to_string() if given interface is a field of a class that emits to_string() // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Common.h" #include "V3EmitCBase.h" VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Common component builders string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) { std::string stmt; if (VN_IS(nodep->skipRefp(), BasicDType) && nodep->isWide()) { stmt += "VL_TO_STRING_W("; stmt += cvtToStr(nodep->widthWords()); stmt += ", "; } else { stmt += "VL_TO_STRING("; } stmt += lhs; stmt += ")"; return stmt; } static void makeVlToString(AstIface* nodep) { AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"}; funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "* obj"); funcp->isMethod(false); funcp->isConst(false); funcp->isStatic(false); funcp->protect(false); AstNodeExpr* const exprp = new AstCExpr{nodep->fileline(), "obj ? obj->vlNamep : \"null\""}; exprp->dtypeSetString(); funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); nodep->addStmtsp(funcp); } static void makeVlToString(AstNodeUOrStructDType* nodep) { AstNodeModule* const modp = nodep->classOrPackagep(); UASSERT_OBJ(modp, nodep, "Unlinked struct package"); AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"}; funcp->argTypes("const " + EmitCUtil::prefixNameProtect(nodep) + "& obj"); funcp->isMethod(false); funcp->isConst(false); funcp->isStatic(false); funcp->protect(false); funcp->addStmtsp(new AstCStmt{nodep->fileline(), "std::string out;"}); for (const AstMemberDType* itemp = nodep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { std::string stmt = "out += \""; if (itemp == nodep->membersp()) { stmt += "'{"; } else { stmt += ", "; } stmt += VIdProtect::protect(itemp->prettyName()) + ":\" + "; stmt += V3Common::makeToStringCall(itemp->dtypep(), "obj."s + itemp->nameProtect()); stmt += ";"; funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt}); } funcp->addStmtsp(new AstCStmt{nodep->fileline(), "out += \"}\";"}); AstCExpr* const exprp = new AstCExpr{nodep->fileline(), "out"}; exprp->dtypeSetString(); funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); modp->addStmtsp(funcp); } static void makeToString(AstClass* nodep) { AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"}; funcp->isConst(true); funcp->isStatic(false); funcp->isVirtual(true); funcp->protect(false); AstCExpr* const exprp = new AstCExpr{nodep->fileline(), R"("'{"s + to_string_middle() + "}")"}; exprp->dtypeSetString(); funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); nodep->addStmtsp(funcp); } static void makeToStringMiddle(AstClass* nodep) { AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string_middle", nullptr, "std::string"}; funcp->isConst(true); funcp->isStatic(false); funcp->protect(false); AstNodeStmt* stmtsp = nullptr; std::string comma; for (AstNode* itemp = nodep->membersp(); itemp; itemp = itemp->nextp()) { if (const auto* const varp = VN_CAST(itemp, Var)) { const AstBasicDType* const basicp = varp->dtypeSkipRefp()->basicp(); if (!varp->isParam() && !varp->isInternal() && !(basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator()))) { string stmt = "out += \""; stmt += comma; comma = ", "; stmt += itemp->origNameProtect(); stmt += ":\" + "; stmt += V3Common::makeToStringCall(itemp->dtypep(), itemp->nameProtect()); stmt += ";"; nodep->user1(true); // So what we extend dumps this stmtsp = AstNode::addNextNull(stmtsp, new AstCStmt{nodep->fileline(), stmt}); } } } if (nodep->extendsp()) { string stmt = "out += "; if (!comma.empty()) stmt += "\", \" + "; // comma = ", "; // Nothing further so not needed stmt += EmitCUtil::prefixNameProtect(nodep->extendsp()->dtypep()); stmt += "::to_string_middle();"; nodep->user1(true); // So what we extend dumps this stmtsp = AstNode::addNextNull(stmtsp, new AstCStmt{nodep->fileline(), stmt}); } AstNodeExpr* exprp; if (stmtsp) { funcp->addStmtsp(new AstCStmt{nodep->fileline(), "std::string out;"}); funcp->addStmtsp(stmtsp); exprp = new AstCExpr{nodep->fileline(), "out"}; exprp->dtypeSetString(); } else { // Nothing to print, return "" exprp = new AstConst{nodep->fileline(), AstConst::String{}, ""}; } funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); nodep->addStmtsp(funcp); } class ToStringVisitor final : public VNVisitorConst { // NODE STATE // AstNode::user1() -> bool. to_string() was emitted // AstClass::user2() -> PrintedFromParent. Used to propagate emission of to_string() from // parent to child const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; size_t m_emitTowardsRoot = 0; // class that require its ancestors to emit to_string() sets this size_t m_emitForMembers = 0; // class/struct that requires its members (and their members // recursively) to emit to_string() sets this enum class PrintedFromParent : uint8_t { UNRESOLVED, PRINTED_FROM_PARENT, NO_PRINTED_FROM_PARENT }; VDouble0 m_statClassToString; VDouble0 m_statInterfaceToString; VDouble0 m_statStructUnionToString; void markClassIterate(AstClass* classp) { if (classp->user1SetOnce()) return; VL_RESTORER(m_emitForMembers); ++m_emitForMembers; for (AstNode* stmtp = classp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (AstVar* const varp = VN_CAST(stmtp, Var)) { AstNodeDType* nodeDtypep = varp->dtypep(); if (AstIfaceRefDType* const ifaceRedDTypep = VN_CAST(nodeDtypep, IfaceRefDType)) { if (ifaceRedDTypep->ifacep()) iterateConst(ifaceRedDTypep->ifacep()); } else { while (nodeDtypep && nodeDtypep->subDTypep() && nodeDtypep->subDTypep()->skipRefp()) { nodeDtypep = nodeDtypep->subDTypep()->skipRefp(); if (AstIfaceRefDType* const ifaceRedDTypep = VN_CAST(nodeDtypep, IfaceRefDType)) { if (ifaceRedDTypep->ifacep()) { iterateConst(ifaceRedDTypep->ifacep()); } } else if (AstNodeUOrStructDType* const uOrStructDTypep = VN_CAST(nodeDtypep, NodeUOrStructDType)) { iterateConst(uOrStructDTypep); } } } } } makeToString(classp); makeToStringMiddle(classp); ++m_statClassToString; } void visit(AstNodeUOrStructDType* structp) override { if (structp->user1()) return; // if to_string() was already emitted skip the rest of the function if (structp->emitToString()) ++m_emitForMembers; if (m_emitForMembers > 0) { // either this struct directly requires emission of to_string() or the // class/struct that has this struct as a field requires it to emit to_string() for (AstMemberDType* memberp = structp->membersp(); memberp; memberp = VN_AS(memberp->nextp(), MemberDType)) { AstNodeDType* nodeDtypep = memberp->dtypep(); if (AstNodeUOrStructDType* const uOrStructDTypep = VN_CAST(nodeDtypep, NodeUOrStructDType)) { iterateConst(uOrStructDTypep); } else { while (nodeDtypep && nodeDtypep->subDTypep()) { nodeDtypep = nodeDtypep->subDTypep()->skipRefp(); if (AstNodeUOrStructDType* const uOrStructDTypep = VN_CAST(nodeDtypep, NodeUOrStructDType)) { iterateConst(uOrStructDTypep); } } } } if (structp->emitToString()) --m_emitForMembers; if (!structp->packed()) { makeVlToString(structp); ++m_statStructUnionToString; } structp->user1(true); // mark that to_string() was emitted to not emit it twice (if // e.g two printed classes have this struct as a field) } } void visit(AstIface* ifacep) override { if (ifacep->user1()) return; // if to_string() was already emitted skip the rest of the function if (m_emitForMembers > 0) { // class that has this iface as a field requires it to emit to_string() makeVlToString(ifacep); ++m_statInterfaceToString; ifacep->user1(true); // mark that to_string() was emitted to not emit it twice (if e.g // two printed classes have this interface as a field) } } // go towards class hierarchy root, if there is any printed from class on the way, mark its // ancestors, and mark everything on the return path from the printed from class void visit(AstClass* classp) override { if (classp->user1()) return; if (classp->isPrintedFrom()) { markClassIterate(classp); VL_RESTORER(m_emitTowardsRoot); ++m_emitTowardsRoot; for (const AstClassExtends* extendp = classp->extendsp(); extendp; extendp = VN_AS(extendp->nextp(), ClassExtends)) { if (extendp->classp() && !extendp->classp()->user1()) { iterateConst(extendp->classp()); } } classp->user2(static_cast(PrintedFromParent::PRINTED_FROM_PARENT)); } else { PrintedFromParent printed_parent = static_cast(classp->user2()); if (printed_parent != PrintedFromParent::UNRESOLVED && m_emitTowardsRoot == 0) { return; } if (m_emitTowardsRoot > 0) markClassIterate(classp); bool is_root = true; for (const AstClassExtends* extendp = classp->extendsp(); extendp; extendp = VN_AS(extendp->nextp(), ClassExtends)) { if (extendp->classp()) { if (static_cast(extendp->classp()->user2()) == PrintedFromParent::UNRESOLVED || (m_emitTowardsRoot > 0 && !extendp->classp()->user1())) { iterateConst(extendp->classp()); } if (printed_parent != PrintedFromParent::PRINTED_FROM_PARENT) { printed_parent = static_cast(extendp->classp()->user2()); } is_root = false; } } if (is_root) printed_parent = PrintedFromParent::NO_PRINTED_FROM_PARENT; classp->user2(static_cast(printed_parent)); if (printed_parent == PrintedFromParent::PRINTED_FROM_PARENT) { markClassIterate(classp); // If one parent is printed from, other parent also has to emit to_string() to // handle cases like: // Base IFaceClass (printed from) // V V // Derived (this class) VL_RESTORER(m_emitTowardsRoot); ++m_emitTowardsRoot; for (const AstClassExtends* extendp = classp->extendsp(); extendp; extendp = VN_AS(extendp->nextp(), ClassExtends)) { if (extendp->classp() && !extendp->classp()->user1()) { iterateConst(extendp->classp()); } } } } } void visit(AstConstPool*) override {} // Accelerate void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } public: explicit ToStringVisitor(AstNode* nodep) { if (v3Global.hasPrintedObjects()) { AstNode::user1ClearTree(); AstNode::user2ClearTree(); iterateConst(nodep); } V3Stats::addStat("Optimizations, Class ToString emitted", m_statClassToString); V3Stats::addStat("Optimizations, Interface ToString emitted", m_statInterfaceToString); V3Stats::addStat("Optimizations, Struct/union ToString emitted", m_statStructUnionToString); } ~ToStringVisitor() override = default; }; //###################################################################### // V3Common class functions void V3Common::commonAll() { UINFO(2, __FUNCTION__ << ":"); ToStringVisitor{v3Global.rootp()}; V3Global::dumpCheckGlobalTree("common", 0, dumpTreeEitherLevel() >= 3); }