Fix circular class reference %p-printing causing infinite recursion (#7106).
Fixes #7106.
This commit is contained in:
parent
4a4f8c6698
commit
0d2fcfd49d
1
Changes
1
Changes
|
|
@ -114,6 +114,7 @@ Verilator 5.045 devel
|
|||
* Fix constant propagating DPI-written variables (#7074). [Geza Lore, Testorrent USA, Inc.]
|
||||
* Fix conditional expressions in constraints (#7087). [Ryszard Rozak, Antmicro Ltd.]
|
||||
* Fix time to not advance after `$finish` (#7095).
|
||||
* Fix circular class reference %p-printing causing infinite recursion (#7106).
|
||||
|
||||
|
||||
Verilator 5.044 2026-01-01
|
||||
|
|
|
|||
|
|
@ -1884,6 +1884,9 @@ public:
|
|||
VlClass() {}
|
||||
VlClass(const VlClass& copied) {}
|
||||
~VlClass() override = default;
|
||||
// METHODS
|
||||
virtual const char* typeName() const { return "VlClass"; }
|
||||
virtual std::string to_string() const { return ""; }
|
||||
};
|
||||
|
||||
//===================================================================
|
||||
|
|
@ -2045,6 +2048,30 @@ static inline bool VL_CAST_DYNAMIC(VlNull in, VlClassRef<T_Lhs>& outr) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// For printing class references under a container, several choices:
|
||||
// 1. Dump recursively the pointed-to object. Can be huge. Might be circular.
|
||||
// 2. Print object type and pointer as C pointer. Astable when rerun.
|
||||
// 3. Print object type and pointer as an incrementing number. Needs num storage.
|
||||
// 4. Print object type alone. Avoids above issues.
|
||||
template <typename T_Lhs>
|
||||
inline std::string VL_TO_STRING(const VlClassRef<T_Lhs>& obj) {
|
||||
return obj ? obj->typeName() : "null";
|
||||
}
|
||||
// Entry point for string conversion (called from not under a container);
|
||||
// dereference VlClassRef objects to print members
|
||||
template <typename T_Lhs> // Default if no specialization
|
||||
inline std::string VL_TO_STRING_DEREF(T_Lhs obj) {
|
||||
return VL_TO_STRING(obj);
|
||||
}
|
||||
template <typename T_Lhs> // Specialization
|
||||
inline std::string VL_TO_STRING_DEREF(const VlClassRef<T_Lhs>& obj) {
|
||||
return obj ? obj->to_string() : "null";
|
||||
}
|
||||
template <typename T_Lhs> // Specialization
|
||||
inline std::string VL_TO_STRING_DEREF(VlClassRef<T_Lhs>& obj) {
|
||||
return obj ? obj->to_string() : "null";
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// VlSampleQueue stores samples for input clockvars in clocking blocks. At a clocking event,
|
||||
// samples from this queue should be written to the correct input clockvar.
|
||||
|
|
|
|||
|
|
@ -5553,7 +5553,9 @@ public:
|
|||
ASTGEN_MEMBERS_AstToStringN;
|
||||
void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
|
||||
string emitVerilog() override { return "$sformatf(\"%p\", %l)"; }
|
||||
string emitC() override { return isWide() ? "VL_TO_STRING_W(%nw, %li)" : "VL_TO_STRING(%li)"; }
|
||||
string emitC() override {
|
||||
return isWide() ? "VL_TO_STRING_W(%nw, %li)" : "VL_TO_STRING_DEREF(%li)";
|
||||
}
|
||||
bool cleanOut() const override { return true; }
|
||||
bool cleanLhs() const override { return true; }
|
||||
bool sizeMattersLhs() const override { return false; }
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ class AstCFunc final : public AstNode {
|
|||
// an explicitly passed 'self' pointer as the first argument. This can
|
||||
// be slightly faster due to __restrict, and we do not declare in header
|
||||
// so adding/removing loose functions doesn't recompile everything.
|
||||
bool m_isOverride : 1; // Override virtual function
|
||||
bool m_isVirtual : 1; // Virtual function
|
||||
bool m_entryPoint : 1; // User may call into this top level function
|
||||
bool m_dpiPure : 1; // Pure DPI function
|
||||
|
|
@ -519,6 +520,7 @@ public:
|
|||
m_isDestructor = false;
|
||||
m_isMethod = true;
|
||||
m_isLoose = false;
|
||||
m_isOverride = false;
|
||||
m_isVirtual = false;
|
||||
m_needProcess = false;
|
||||
m_entryPoint = false;
|
||||
|
|
@ -582,6 +584,11 @@ public:
|
|||
bool isLoose() const { return m_isLoose; }
|
||||
void isLoose(bool flag) { m_isLoose = flag; }
|
||||
bool isProperMethod() const { return isMethod() && !isLoose(); }
|
||||
bool isOverride() const { return m_isOverride; }
|
||||
void isOverride(bool flag) {
|
||||
m_isOverride = flag;
|
||||
if (flag) isVirtual(true);
|
||||
}
|
||||
bool isVirtual() const { return m_isVirtual; }
|
||||
void isVirtual(bool flag) { m_isVirtual = flag; }
|
||||
bool needProcess() const { return m_needProcess; }
|
||||
|
|
|
|||
|
|
@ -3233,6 +3233,7 @@ void AstCFunc::dump(std::ostream& str) const {
|
|||
if (isDestructor()) str << " [DTOR]";
|
||||
if (isMethod()) str << " [METHOD]";
|
||||
if (isLoose()) str << " [LOOSE]";
|
||||
if (isOverride()) str << " [OVERRIDE]";
|
||||
if (isVirtual()) str << " [VIRT]";
|
||||
if (isCoroutine()) str << " [CORO]";
|
||||
if (needProcess()) str << " [NPRC]";
|
||||
|
|
@ -3249,6 +3250,7 @@ void AstCFunc::dumpJson(std::ostream& str) const {
|
|||
dumpJsonBoolFuncIf(str, dpiContext);
|
||||
dumpJsonBoolFuncIf(str, isConstructor);
|
||||
dumpJsonBoolFuncIf(str, isDestructor);
|
||||
dumpJsonBoolFuncIf(str, isOverride);
|
||||
dumpJsonBoolFuncIf(str, isVirtual);
|
||||
dumpJsonBoolFuncIf(str, isCoroutine);
|
||||
dumpJsonBoolFuncIf(str, needProcess);
|
||||
|
|
|
|||
|
|
@ -37,10 +37,6 @@ string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
|
|||
stmt += "VL_TO_STRING_W(";
|
||||
stmt += cvtToStr(nodep->widthWords());
|
||||
stmt += ", ";
|
||||
} else if (VN_IS(nodep->skipRefp(), BasicDType) && nodep->isWide()) {
|
||||
stmt += "VL_TO_STRING_W(";
|
||||
stmt += cvtToStr(nodep->widthWords());
|
||||
stmt += ", ";
|
||||
} else {
|
||||
stmt += "VL_TO_STRING(";
|
||||
}
|
||||
|
|
@ -49,20 +45,6 @@ string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
|
|||
return stmt;
|
||||
}
|
||||
|
||||
static void makeVlToString(AstClass* nodep) {
|
||||
AstCFunc* const funcp
|
||||
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
|
||||
funcp->argTypes("const VlClassRef<" + 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->to_string() : \"null\""};
|
||||
exprp->dtypeSetString();
|
||||
funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
|
||||
nodep->addStmtsp(funcp);
|
||||
}
|
||||
static void makeVlToString(AstIface* nodep) {
|
||||
AstCFunc* const funcp
|
||||
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
|
||||
|
|
@ -112,6 +94,7 @@ static void makeToString(AstClass* nodep) {
|
|||
AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"};
|
||||
funcp->isConst(true);
|
||||
funcp->isStatic(false);
|
||||
funcp->isOverride(true);
|
||||
funcp->protect(false);
|
||||
AstCExpr* const exprp = new AstCExpr{nodep->fileline(), R"("'{"s + to_string_middle() + "}")"};
|
||||
exprp->dtypeSetString();
|
||||
|
|
@ -128,10 +111,9 @@ static void makeToStringMiddle(AstClass* nodep) {
|
|||
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()
|
||||
&& !(varp->dtypeSkipRefp()->basicp()
|
||||
&& (varp->dtypeSkipRefp()->basicp()->isRandomGenerator()
|
||||
|| varp->dtypeSkipRefp()->basicp()->isStdRandomGenerator()))) {
|
||||
&& !(basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator()))) {
|
||||
string stmt = "out += \"";
|
||||
stmt += comma;
|
||||
comma = ", ";
|
||||
|
|
@ -146,7 +128,7 @@ static void makeToStringMiddle(AstClass* nodep) {
|
|||
}
|
||||
if (nodep->extendsp()) {
|
||||
string stmt = "out += ";
|
||||
if (!comma.empty()) stmt += "\", \"+ ";
|
||||
if (!comma.empty()) stmt += "\", \" + ";
|
||||
// comma = ", "; // Nothing further so not needed
|
||||
stmt += EmitCUtil::prefixNameProtect(nodep->extendsp()->dtypep());
|
||||
stmt += "::to_string_middle();";
|
||||
|
|
@ -174,7 +156,6 @@ void V3Common::commonAll() {
|
|||
for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) {
|
||||
if (AstClass* const classp = VN_CAST(nodep, Class)) {
|
||||
// Create ToString methods
|
||||
makeVlToString(classp);
|
||||
makeToString(classp);
|
||||
makeToStringMiddle(classp);
|
||||
} else if (AstIface* const ifacep = VN_CAST(nodep, Iface)) {
|
||||
|
|
|
|||
|
|
@ -153,9 +153,10 @@ void EmitCBaseVisitorConst::emitCFuncDecl(const AstCFunc* funcp, const AstNodeMo
|
|||
if (funcp->isStatic() && funcp->isProperMethod()) putns(funcp, "static ");
|
||||
if (funcp->isVirtual()) {
|
||||
UASSERT_OBJ(funcp->isProperMethod(), funcp, "Virtual function is not a proper method");
|
||||
putns(funcp, "virtual ");
|
||||
if (!funcp->isOverride()) putns(funcp, "virtual ");
|
||||
}
|
||||
emitCFuncHeader(funcp, modp, /* withScope: */ false);
|
||||
if (funcp->isOverride()) putns(funcp, " override");
|
||||
if (funcp->emptyBody() && !funcp->isLoose() && !cLinkage) {
|
||||
putns(funcp, " {}\n");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -183,6 +183,11 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
if (!VN_IS(modp, Class)) {
|
||||
decorateFirst(first, section);
|
||||
puts("void " + protect("__Vconfigure") + "(bool first);\n");
|
||||
} else {
|
||||
decorateFirst(first, section);
|
||||
const std::string name = V3OutFormatter::quoteNameControls(
|
||||
VIdProtect::protectWordsIf(modp->prettyName(), v3Global.opt.protectIds()));
|
||||
puts("const char* typeName() const override { return \""s + name + "\"; }\n");
|
||||
}
|
||||
|
||||
if (v3Global.opt.coverage() && !VN_IS(modp, Class)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
''{b:'h1, i:'h2a, carray4:'{'h11, 'h22, 'h33, 'h44}, cwide:'{'h0, 'h0}, name:"object_name", r:2.2}'
|
||||
''{b:'h1, i:'h2a, carray4:'{'h911, 'h922, 'h933, 'h944}, cwide:'{'h0, 'h0}, name:"object_name", r:2.2}'
|
||||
DEBUG: object_name (@0) message
|
||||
''{m_in_a:'h0, m_b:null, m_in_base:'h0}'
|
||||
''{m_in_a:'h0, m_b:$unit::ClsB, m_in_base:'h0}'
|
||||
''{m_in_a:'h0, m_b:$unit::ClsB, m_in_base:'h0}'
|
||||
*-* All Finished *-*
|
||||
|
|
|
|||
|
|
@ -4,46 +4,64 @@
|
|||
// SPDX-FileCopyrightText: 2021 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`ifdef verilator
|
||||
`define stop $stop
|
||||
`else
|
||||
`define stop
|
||||
`endif
|
||||
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
|
||||
class Cls;
|
||||
bit b;
|
||||
int i;
|
||||
bit [15:0] carray4 [4];
|
||||
bit [64:0] cwide[2];
|
||||
string name;
|
||||
real r;
|
||||
task debug();
|
||||
$display("DEBUG: %s (@%0t) %s", this.name, $realtime, "message");
|
||||
endtask
|
||||
bit b;
|
||||
int i;
|
||||
bit [15:0] carray4[4];
|
||||
bit [64:0] cwide[2];
|
||||
string name;
|
||||
real r;
|
||||
task debug();
|
||||
$display("DEBUG: %s (@%0t) %s", this.name, $realtime, "message");
|
||||
endtask
|
||||
endclass
|
||||
|
||||
class Base;
|
||||
int m_in_base;
|
||||
endclass
|
||||
|
||||
class ClsA extends Base;
|
||||
int m_in_a;
|
||||
Base m_b;
|
||||
endclass
|
||||
|
||||
class ClsB extends Base;
|
||||
int m_in_b;
|
||||
Base m_a;
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
Cls c;
|
||||
c = new;
|
||||
c.b = '1;
|
||||
c.i = 42;
|
||||
c.r = 2.2;
|
||||
c.name = "object_name";
|
||||
initial begin
|
||||
Cls c;
|
||||
ClsA ca;
|
||||
ClsB cb;
|
||||
|
||||
c.carray4[0] = 16'h11;
|
||||
c.carray4[1] = 16'h22;
|
||||
c.carray4[2] = 16'h33;
|
||||
c.carray4[3] = 16'h44;
|
||||
$display("'%p'", c);
|
||||
c = new;
|
||||
c.b = '1;
|
||||
c.i = 42;
|
||||
c.r = 2.2;
|
||||
c.name = "object_name";
|
||||
|
||||
c.carray4 = '{16'h911, 16'h922, 16'h933, 16'h944};
|
||||
$display("'%p'", c);
|
||||
c.carray4[0] = 16'h11;
|
||||
c.carray4[1] = 16'h22;
|
||||
c.carray4[2] = 16'h33;
|
||||
c.carray4[3] = 16'h44;
|
||||
$display("'%p'", c);
|
||||
|
||||
c.debug();
|
||||
c.carray4 = '{16'h911, 16'h922, 16'h933, 16'h944};
|
||||
$display("'%p'", c);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
c.debug();
|
||||
|
||||
ca = new;
|
||||
$display("'%p'", ca);
|
||||
cb = new;
|
||||
ca.m_b = cb;
|
||||
$display("'%p'", ca);
|
||||
cb.m_a = ca; // Circular
|
||||
$display("'%p'", ca);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
|
|||
Loading…
Reference in New Issue