diff --git a/include/verilated_types.h b/include/verilated_types.h index da8c94977..b0ef73039 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1937,11 +1937,26 @@ public: VlClassRef(VlNull){}; template VlClassRef(VlDeleter& deleter, T_Args&&... args) - // () required here to avoid narrowing conversion warnings, - // when a new() has an e.g. CData type and passed a 1U. - : m_objp{new T_Class(std::forward(args)...)} { - // refCountInc was moved to the constructor of T_Class - // to fix self references in constructor. + : m_objp{new T_Class} { + // Instantly init the object to presevrve RAII + m_objp->init(std::forward(args)...); + m_objp->m_deleterp = &deleter; + } + VlClassRef(VlDeleter& deleter, T_Class&& args) + // Move constructor + : m_objp{new T_Class{std::forward(args)}} { + m_objp->m_deleterp = &deleter; + } + VlClassRef(VlDeleter& deleter, const T_Class& args) + // Copy constructor + : m_objp{new T_Class{args}} { + m_objp->m_deleterp = &deleter; + } + VlClassRef(VlDeleter& deleter, T_Class& args) + // Copy constructor - this is required since if `T_Class&` + // will be provided a compiler will match it to the constructor + // with variadic template instead of `T_Class&&` + : m_objp{new T_Class{args}} { m_objp->m_deleterp = &deleter; } // Explicit to avoid implicit conversion from 0 diff --git a/src/V3CCtors.cpp b/src/V3CCtors.cpp index 0c4092286..68bfb493d 100644 --- a/src/V3CCtors.cpp +++ b/src/V3CCtors.cpp @@ -149,6 +149,10 @@ class CCtorsVisitor final : public VNVisitor { // VISITORS void visit(AstNodeModule* nodep) override { + if (const AstClass* const classp = VN_CAST(nodep, Class)) { + // Interface class may only have pure virtuals and params which do not need cctor reset + if (classp->isInterfaceClass()) return; + } VL_RESTORER(m_modp); VL_RESTORER(m_varResetp); m_modp = nodep; @@ -199,6 +203,8 @@ class CCtorsVisitor final : public VNVisitor { m_varResetp->add(crstp); } else if (m_cfuncp) { nodep->addNextHere(crstp); + } else { + nodep->v3fatalSrc("Var needs CReset but nowhere to place it"); } } } diff --git a/src/V3EmitCBase.cpp b/src/V3EmitCBase.cpp index 667983199..64d4b4cdd 100644 --- a/src/V3EmitCBase.cpp +++ b/src/V3EmitCBase.cpp @@ -78,7 +78,7 @@ string EmitCBaseVisitorConst::funcNameProtect(const AstCFunc* nodep, const AstNo modp = modp ? modp : EmitCParentModule::get(nodep); string name; if (nodep->isConstructor()) { - name += EmitCUtil::prefixNameProtect(modp); + name += "init"; } else if (nodep->isDestructor()) { name += "~"; name += EmitCUtil::prefixNameProtect(modp); @@ -126,10 +126,15 @@ string EmitCBaseVisitorConst::cFuncArgs(const AstCFunc* nodep) { return args; } +void EmitCBaseVisitorConst::emitCDefaultConstructor(const AstNodeModule* const modp) { + puts(EmitCUtil::prefixNameProtect(modp)); + puts("() = default;\n"); +} + void EmitCBaseVisitorConst::emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp, bool withScope) { if (funcp->slow()) putns(funcp, "VL_ATTR_COLD "); - if (!funcp->isConstructor() && !funcp->isDestructor()) { + if (!funcp->isDestructor()) { putns(funcp, funcp->rtnTypeVoid()); puts(" "); } @@ -149,6 +154,7 @@ void EmitCBaseVisitorConst::emitCFuncDecl(const AstCFunc* funcp, const AstNodeMo bool cLinkage) { ensureNewLine(); if (!funcp->ifdef().empty()) putns(funcp, "#ifdef " + funcp->ifdef() + "\n"); + if (funcp->isConstructor()) emitCDefaultConstructor(modp); if (cLinkage) putns(funcp, "extern \"C\" "); if (funcp->isStatic() && funcp->isProperMethod()) putns(funcp, "static "); if (funcp->isVirtual()) { @@ -158,7 +164,9 @@ void EmitCBaseVisitorConst::emitCFuncDecl(const AstCFunc* funcp, const AstNodeMo // on other methods where virtual vs override is needed, and this is not tracked yet } emitCFuncHeader(funcp, modp, /* withScope: */ false); - if (funcp->emptyBody() && !funcp->isLoose() && !cLinkage) { + const AstClass* const classp = VN_CAST(modp, Class); + if (funcp->emptyBody() && !funcp->isLoose() && !cLinkage + && !(funcp->isConstructor() && classp && classp->isInterfaceClass())) { putns(funcp, " {}\n"); } else { putns(funcp, ";\n"); diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index 236c095c0..85e35d5f9 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -183,6 +183,7 @@ public: static string protect(const string& name) VL_MT_SAFE { return VIdProtect::protect(name); } static string funcNameProtect(const AstCFunc* nodep, const AstNodeModule* modp = nullptr); string cFuncArgs(const AstCFunc* nodep); + void emitCDefaultConstructor(const AstNodeModule* modp); void emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp, bool withScope); void emitCFuncDecl(const AstCFunc* funcp, const AstNodeModule* modp, bool cLinkage = false); void emitVarDecl(const AstVar* nodep, bool asRef = false); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index df70952c9..2501a2a33 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -336,25 +336,28 @@ public: std::unordered_set doneClasses; collectVirtualBasesRecursep(classp, virtualBases); for (AstClass* vbase : virtualBases) { + if (vbase->isInterfaceClass()) continue; if (doneClasses.count(vbase)) continue; - puts(doneClasses.empty() ? "" : "\n , "); doneClasses.emplace(vbase); puts(EmitCUtil::prefixNameProtect(vbase)); + puts("::init"); if (constructorNeedsProcess(vbase)) { - puts("(vlProcess, vlSymsp)"); + puts("(vlProcess, vlSymsp);"); } else { - puts("(vlSymsp)"); + puts("(vlSymsp);"); } + puts("\n"); } const AstCNew* const superNewCallp = getSuperNewCallRecursep(cfuncp->stmtsp()); // Direct non-virtual bases in declaration order for (const AstClassExtends* extp = classp->extendsp(); extp; extp = VN_AS(extp->nextp(), ClassExtends)) { + if (extp->classp()->isInterfaceClass()) continue; if (extp->classp()->useVirtualPublic()) continue; if (doneClasses.count(extp->classp())) continue; - puts(doneClasses.empty() ? "" : "\n , "); doneClasses.emplace(extp->classp()); puts(EmitCUtil::prefixNameProtect(extp->classp())); + puts("::init"); if (constructorNeedsProcess(extp->classp())) { puts("(vlProcess, vlSymsp"); } else { @@ -364,7 +367,7 @@ public: if (!extp->classp()->isInterfaceClass() && superNewCallp) { putCommaIterateNext(superNewCallp->argsp(), true); } - puts(")"); + puts(");\n"); } } void collectVirtualBasesRecursep(const AstClass* classp, @@ -403,14 +406,11 @@ public: if (nodep->ifdef() != "") putns(nodep, "#ifdef " + nodep->ifdef() + "\n"); emitCFuncHeader(nodep, m_modp, /* withScope: */ true); + puts(" {\n"); if (nodep->isConstructor()) { const AstClass* const classp = VN_CAST(nodep->scopep()->modp(), Class); - if (classp && classp->extendsp()) { - puts("\n : "); - putConstructorSubinit(classp, nodep); - } + if (classp && classp->extendsp()) putConstructorSubinit(classp, nodep); } - puts(" {\n"); // "+" in the debug indicates a print from the model puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ "); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a482a04f5..4203eaa14 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -5299,7 +5299,10 @@ class LinkDotResolveVisitor final : public VNVisitor { << classExtendsp->argsp()->warnContextSecondary()); } if (classExtendsp && classExtendsp->classOrNullp()) { - if (!m_explicitSuperNewp && m_statep->forParamed()) { + if (!m_explicitSuperNewp && m_statep->forParamed() + && !VN_AS(classExtendsp->childDTypep()->skipRefp(), ClassRefDType) + ->classp() + ->isInterfaceClass()) { AstNodeStmt* const superNewp = addImplicitSuperNewCall(VN_AS(nodep, Func), classExtendsp); UINFO(9, "created super new " << superNewp); diff --git a/src/V3Localize.cpp b/src/V3Localize.cpp index edafffdee..03d50a6ff 100644 --- a/src/V3Localize.cpp +++ b/src/V3Localize.cpp @@ -45,9 +45,6 @@ class LocalizeVisitor final : public VNVisitor { // AstVarScope::user3p() -> Set of CFuncs referencing this VarScope. (via m_accessors) // AstCFunc::user4p() -> Multimap of 'VarScope -> VarRefs that reference that VarScope' // in this function. (via m_references) - // AstVarScope::user4() -> Bool indicating VarScope cannot be optimized - // - compared to AstVarScope::user1 this guarantees that this - // scope won't be optimized. const VNUser1InUse m_user1InUse; const VNUser3InUse m_user3InUse; const VNUser4InUse m_user4InUse; @@ -63,7 +60,6 @@ class LocalizeVisitor final : public VNVisitor { // STATE - for current visit position (use VL_RESTORER) AstCFunc* m_cfuncp = nullptr; // Current active function uint32_t m_nodeDepth = 0; // Node depth under m_cfuncp - bool m_inSuperConstructorCallStmt = false; // If under super constructor call statement // METHODS bool isOptimizable(AstVarScope* nodep) { @@ -76,7 +72,6 @@ class LocalizeVisitor final : public VNVisitor { if (nodep->dtypep()->skipRefp()->isString()) return false; // Variables used in super constructor call can't be localized, because // in C++ there is no way to declare them before base class constructor call - if (nodep->user4()) return false; return ((!nodep->user1() // Not marked as not optimizable, or ... // .. a block temp used in a single CFunc || (nodep->varp()->varType() == VVarType::BLOCKTEMP @@ -160,9 +155,6 @@ class LocalizeVisitor final : public VNVisitor { } void visit(AstCNew* nodep) override { - VL_RESTORER(m_inSuperConstructorCallStmt); - m_inSuperConstructorCallStmt - = m_cfuncp->isConstructor() && VN_IS(nodep->backp(), StmtExpr); m_cfuncp->user1(true); // Mark caller as not a leaf function iterateChildren(nodep); } @@ -216,11 +208,7 @@ class LocalizeVisitor final : public VNVisitor { // Remember the reference so we can fix it up later (we always need this as well) m_references(m_cfuncp).emplace(varScopep, nodep); - if (m_inSuperConstructorCallStmt) { - // Variable used in super constructor call can't be localized - varScopep->user1(true); - varScopep->user4(true); - } else if (!varScopep->user1()) { // Check if already marked as not optimizable + if (!varScopep->user1()) { // Check if already marked as not optimizable // Note: we only check read variables, as it's ok to localize (and in fact discard) // any variables that are only written but never read. if (nodep->access().isReadOrRW() && !varScopep->user2()) { diff --git a/src/V3Task.cpp b/src/V3Task.cpp index a5da23eeb..29bb1e0b0 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1338,9 +1338,15 @@ class TaskVisitor final : public VNVisitor { if (!nodep->dpiImport() && !nodep->taskPublic()) { // Need symbol table - if (cfuncp->name() == "new") { - const string stmt = VIdProtect::protect("_ctor_var_reset") + "(vlSymsp);"; - cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt}); + if (cfuncp->isConstructor()) { + bool isInterfaceClass = false; + if (const AstClass* const classp = VN_CAST(m_modp, Class)) { + isInterfaceClass = classp->isInterfaceClass(); + } + if (!isInterfaceClass) { + const string stmt = VIdProtect::protect("_ctor_var_reset") + "(vlSymsp);"; + cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt}); + } } } if (nodep->dpiContext()) { diff --git a/test_regress/t/t_class_new_base_call.py b/test_regress/t/t_class_new_base_call.py new file mode 100755 index 000000000..e41ab0cdd --- /dev/null +++ b/test_regress/t/t_class_new_base_call.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_new_base_call.v b/test_regress/t/t_class_new_base_call.v new file mode 100644 index 000000000..fcb6d3f4a --- /dev/null +++ b/test_regress/t/t_class_new_base_call.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class Base; + int k = 3; + function new(int x); + k = x; + endfunction + protected function int get_x_base(); + Base f = this; + return 7; + endfunction +endclass + +class Foo extends Base; + function new(int x); + super.new(x); + endfunction + + protected function int get_x(); + Foo f = this; + return get_x_base(); + endfunction +endclass + +class Bar extends Foo; + function new(); + super.new(get_x()); + endfunction +endclass + +module top; + initial begin + static Bar b = new(); + if (b.k != 7) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule