mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
The constant pool is now an ordinary package, created with the netlist and instantiated under $root like any other package, and most special handling has been removed, including AstConstPool. Lookups go through V3ConstPool, which has a singleton instance owned by V3Global. Static methods on V3Common form the public interfce to add/find constant pool entries. With that, the constant pool is usable at any stage during compilation, so enum and dimension tables created by V3Width, and enum value tables created by V3Randomize now also live in the constant pool instead of $unit, so identical tables are shared. Associative array constants are handled separately from unpacked tables, which used to be broken but unused. Emitted constant pool variables use direct initialization, and `constinit` with C++20 where the type allows it. This ensures we don't change a run-time in a way that would result in unintended code size increase. -fno-merge-const-pool, which was introduced years ago but never prompted a bug report is deprecated and has no effect. This is also prep for future work.
350 lines
15 KiB
C++
350 lines
15 KiB
C++
// -*- 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<int>(PrintedFromParent::PRINTED_FROM_PARENT));
|
|
} else {
|
|
PrintedFromParent printed_parent = static_cast<PrintedFromParent>(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<PrintedFromParent>(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<PrintedFromParent>(extendp->classp()->user2());
|
|
}
|
|
is_root = false;
|
|
}
|
|
}
|
|
|
|
if (is_root) printed_parent = PrintedFromParent::NO_PRINTED_FROM_PARENT;
|
|
|
|
classp->user2(static_cast<int>(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(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);
|
|
}
|