Optimize away empty ctor_var_reset.

Fixes #7154.
This commit is contained in:
Wilson Snyder 2026-03-23 18:10:34 -04:00
parent 06263ec724
commit 716b404256
11 changed files with 102 additions and 52 deletions

View File

@ -997,6 +997,48 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
// ######################################################################
class VCStmtType final {
public:
enum en : uint8_t {
NONE, // Unknown or not applicable
CTOR_VAR_RESET_CALL,
_ENUM_MAX // Leave last
};
private:
struct Item final {
enum en m_e; // Statement's enum mnemonic, for checking
const char* m_name; // Statements name, for debugging
};
static Item s_itemData[];
public:
enum en m_e;
VCStmtType()
: m_e{NONE} {}
// cppcheck-suppress noExplicitConstructor
constexpr VCStmtType(en _e)
: m_e{_e} {}
explicit VCStmtType(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
const char* ascii() const VL_PURE {
static const char* const names[] = {"none", "ctor_var_reset_call"};
return names[m_e];
}
bool isNone() const { return m_e == NONE; }
};
constexpr bool operator==(const VCStmtType& lhs, const VCStmtType& rhs) {
return lhs.m_e == rhs.m_e;
}
constexpr bool operator==(const VCStmtType& lhs, VCStmtType::en rhs) { return lhs.m_e == rhs; }
constexpr bool operator==(VCStmtType::en lhs, const VCStmtType& rhs) { return lhs == rhs.m_e; }
inline std::ostream& operator<<(std::ostream& os, const VCStmtType& rhs) {
return os << rhs.ascii();
}
// ######################################################################
class VCaseType final {
public:
enum en : uint8_t {

View File

@ -280,6 +280,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
bool m_modPublic : 1; // Module has public references
bool m_modTrace : 1; // Tracing this module
bool m_inLibrary : 1; // From a library, no error if not used, never top level
bool m_ctorVarReset : 1; // Ctor needs to call ctor_var_reset
bool m_dead : 1; // LinkDot believes is dead; will remove in Dead visitors
bool m_hasGParam : 1; // Has global parameter (for link)
bool m_hasParameterList : 1; // Has #() for parameter declaration
@ -301,6 +302,7 @@ protected:
, m_modPublic{false}
, m_modTrace{false}
, m_inLibrary{false}
, m_ctorVarReset{false}
, m_dead{false}
, m_hasGParam{false}
, m_hasParameterList{false}
@ -336,6 +338,8 @@ public:
void modPublic(bool flag) { m_modPublic = flag; }
bool modTrace() const { return m_modTrace; }
void modTrace(bool flag) { m_modTrace = flag; }
bool ctorVarReset() const { return m_ctorVarReset; }
void ctorVarReset(bool flag) { m_ctorVarReset = flag; }
bool dead() const { return m_dead; }
void dead(bool flag) { m_dead = flag; }
bool hasGParam() const { return m_hasGParam; }

View File

@ -311,6 +311,7 @@ public:
class AstCStmt final : public AstNodeStmt {
// C statement emitted into output, with some arbitrary nodes interspersed
// @astgen op1 := nodesp : List[AstNode<AstNodeStmt|AstNodeExpr|AstText>]
const VCStmtType m_stmtType; // Special statement (instead of comparing name())
static AstCStmt* profExecSection(FileLine* flp, const std::string& section, bool push) {
// Compute the label
@ -337,8 +338,10 @@ class AstCStmt final : public AstNodeStmt {
}
public:
explicit AstCStmt(FileLine* fl, const std::string& text = "")
: ASTGEN_SUPER_CStmt(fl) {
explicit AstCStmt(FileLine* fl, const std::string& text = "",
const VCStmtType& stmtType = VCStmtType::NONE)
: ASTGEN_SUPER_CStmt(fl)
, m_stmtType{stmtType} {
if (!text.empty()) add(text);
}
ASTGEN_MEMBERS_AstCStmt;
@ -347,6 +350,9 @@ public:
bool isPredictOptimizable() const override { return false; }
bool isPure() override { return false; }
bool sameNode(const AstNode*) const override { return true; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
VCStmtType stmtType() const { return m_stmtType; }
// Add some text, or a node to this statement
void add(const std::string& text) { addNodesp(new AstText{fileline(), text}); }
void add(AstNode* nodep) { addNodesp(nodep); }

View File

@ -2633,6 +2633,7 @@ void AstNodeModule::dump(std::ostream& str) const {
str << " D" << depth();
if (modPublic()) str << " [P]";
if (inLibrary()) str << " [LIB]";
if (ctorVarReset()) str << " [CVRESET]";
if (dead()) str << " [DEAD]";
if (recursiveClone()) {
str << " [RECURSIVE-CLONE]";
@ -2650,6 +2651,7 @@ void AstNodeModule::dumpJson(std::ostream& str) const {
dumpJsonNumFunc(str, level);
dumpJsonBoolFuncIf(str, modPublic);
dumpJsonBoolFuncIf(str, inLibrary);
dumpJsonBoolFuncIf(str, ctorVarReset);
dumpJsonBoolFuncIf(str, dead);
dumpJsonBoolFuncIf(str, recursiveClone);
dumpJsonBoolFuncIf(str, recursive);
@ -3398,6 +3400,14 @@ void AstCMethodHard::setPurity() {
}
}
void AstCStmt::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
if (!stmtType().isNone()) str << " [" << stmtType().ascii() << "]";
}
void AstCStmt::dumpJson(std::ostream& str) const {
dumpJsonGen(str);
if (!stmtType().isNone()) dumpJsonStr(str, "stmtType", stmtType().ascii());
}
void AstCUse::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << useType() << "]";

View File

@ -65,6 +65,7 @@ class V3CCtorsBuilder final {
funcp->keepIfEmpty(true); // TODO relax
funcp->declPrivate(true);
funcp->slow(!m_type.isClass()); // Only classes construct on fast path
if (!m_type.isCoverage()) m_modp->ctorVarReset(true);
string preventUnusedStmt;
if (m_type.isClass()) {
funcp->argTypes(EmitCUtil::symClassVar());
@ -83,6 +84,7 @@ class V3CCtorsBuilder final {
public:
void add(AstNode* nodep) {
if (m_newFunctions.empty()) m_newFunctions.push_back(makeNewFunc());
if (v3Global.opt.outputSplitCFuncs() && m_numStmts > v3Global.opt.outputSplitCFuncs()) {
m_newFunctions.push_back(makeNewFunc());
}
@ -94,13 +96,13 @@ public:
: m_modp{nodep}
, m_basename{basename}
, m_type{type} {
// Note: The constructor is always called, even if empty, so we must always create at least
// one.
m_newFunctions.push_back(makeNewFunc());
// Expect coverage function to always exist, so must always create at least one.
if (m_type.isCoverage()) m_newFunctions.push_back(makeNewFunc());
}
~V3CCtorsBuilder() {
if (m_newFunctions.size() == 1) {
if (m_newFunctions.size() == 0) {
} else if (m_newFunctions.size() == 1) {
// No split was necessary, rename the one function to the basename
m_newFunctions.front()->name(m_basename);
} else {
@ -136,6 +138,7 @@ class CCtorsVisitor final : public VNVisitor {
AstNodeModule* m_modp = nullptr; // Current module
AstCFunc* m_cfuncp = nullptr; // Current function
V3CCtorsBuilder* m_varResetp = nullptr; // Builder of _ctor_var_reset
std::map<AstCStmt*, const AstNodeModule*> m_ctorCalls; // Calls to _ctor_var_reset
// METHODS
static void insertSc(AstCFunc* cfuncp, const AstNodeModule* modp, VSystemCSectionType type) {
@ -194,6 +197,13 @@ class CCtorsVisitor final : public VNVisitor {
iterateChildren(nodep);
if (nodep->name() == "new") insertSc(nodep, m_modp, VSystemCSectionType::CTOR);
}
void visit(AstCStmt* nodep) override {
if (nodep->stmtType() == VCStmtType::CTOR_VAR_RESET_CALL) {
UASSERT_OBJ(m_modp, nodep, "ctor_var_reset call not under module");
m_ctorCalls.emplace(nodep, m_modp);
}
iterateChildren(nodep);
}
void visit(AstVar* nodep) override {
if (nodep->needsCReset()) {
AstNode* const crstp = new AstAssign{
@ -215,7 +225,14 @@ class CCtorsVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit CCtorsVisitor(AstNode* nodep) { iterate(nodep); }
~CCtorsVisitor() override = default;
~CCtorsVisitor() override {
// Remove CStmts to ctor_var_resets that are no longer needed
for (auto& itr : m_ctorCalls) {
AstCStmt* const nodep = itr.first;
const AstNodeModule* const modp = itr.second;
if (!modp->ctorVarReset()) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
}
};
//######################################################################

View File

@ -92,8 +92,9 @@ class EmitCImp final : public EmitCFunc {
const std::string modName = EmitCUtil::prefixNameProtect(modp);
puts("\n");
m_lazyDecls.emit("void " + modName + "__", protect("_ctor_var_reset"),
"(" + modName + "* vlSelf);");
if (modp->ctorVarReset())
m_lazyDecls.emit("void " + modName + "__", protect("_ctor_var_reset"),
"(" + modName + "* vlSelf);");
puts("\n");
const std::string ctorArgs = EmitCUtil::symClassName() + "* symsp, const char* namep";
@ -148,7 +149,7 @@ class EmitCImp final : public EmitCFunc {
}
putsDecoration(modp, "// Reset structure values\n");
puts(modName + "__" + protect("_ctor_var_reset") + "(this);\n");
if (modp->ctorVarReset()) puts(modName + "__" + protect("_ctor_var_reset") + "(this);\n");
emitSystemCSection(modp, VSystemCSectionType::CTOR);
puts("}\n");

View File

@ -285,7 +285,7 @@ private:
if (m_logp) *m_logp << "Buckets assigned to Work Lists:\n";
int availableBuckets = v3Global.opt.outputGroups();
for (WorkList* listp : m_concatenableListsByDescSize) {
if (availableBuckets > 0) {
if (availableBuckets > 0 && idealBucketScore > 0) {
listp->m_bucketsNum = std::min(
availableBuckets, std::max<int>(1, listp->m_totalScore / idealBucketScore));
availableBuckets -= listp->m_bucketsNum;

View File

@ -1345,7 +1345,8 @@ class TaskVisitor final : public VNVisitor {
}
if (!isInterfaceClass) {
const string stmt = VIdProtect::protect("_ctor_var_reset") + "(vlSymsp);";
cfuncp->addStmtsp(new AstCStmt{nodep->fileline(), stmt});
cfuncp->addStmtsp(
new AstCStmt{nodep->fileline(), stmt, VCStmtType::CTOR_VAR_RESET_CALL});
}
}
}

View File

@ -16,10 +16,7 @@ test.compile(verilator_flags2=["--output-groups", "2"])
test.execute()
# Check that only vm_classes_*.cpp are to be compiled
test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "Foo")
test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_1")
test.file_grep(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_1")
test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_Slow_2")
test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2")
test.passes()

View File

@ -4,6 +4,14 @@
// SPDX-FileCopyrightText: 2024 Antmicro Ltd
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`ifdef verilator
`define no_optimize(v) $c(v)
`else
`define no_optimize(v) (v)
`endif
// verilog_format: on
virtual class Base;
pure virtual function int get_param;
endclass
@ -11,7 +19,7 @@ class Foo #(
int N = 17
) extends Base;
function int get_param;
return N;
return `no_optimize(N);
endfunction
endclass

View File

@ -1,55 +1,22 @@
-V{t#,#}- Verilated::debug is on. Message prefix indicates {<thread>,<sequence_number>}.
-V{t#,#}+ Vt_timing_debug2___024root___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay__Vclpkg___ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass__Vclpkg___ctor_var_reset
-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions
-V{t#,#}+ Initial
-V{t#,#}+ Vt_timing_debug2___024root___eval_static
-V{t#,#}+ Vt_timing_debug2_t___eval_static__TOP__t
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::new
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::new
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aWaitClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::new
-V{t#,#}+ Vt_timing_debug2___024unit__03a__03aBaseClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aLocalWaitClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay20::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay40::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aNoDelay::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aAssignDelayClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2___024root___timing_ready
@ -68,7 +35,6 @@
-V{t#,#}+ Vt_timing_debug2_t__03a__03aDelay10::__VnoInFunc_do_delay
-V{t#,#}+ Vt_timing_debug2_t___eval_initial__TOP__t__Vtiming__7
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::_ctor_var_reset
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__VnoInFunc_do_fork
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__VnoInFunc_do_fork____Vfork_1__0
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkClass::__VnoInFunc_do_fork____Vfork_1__1
@ -395,8 +361,6 @@
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:123
-V{t#,#}+ Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_wake
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:261
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::new
-V{t#,#}+ Vt_timing_debug2_t__03a__03aForkDelayClass::_ctor_var_reset
-V{t#,#} Process forked at t/t_timing_class.v:260 finished
-V{t#,#} Resuming: Process waiting at t/t_timing_class.v:135
-V{t#,#}+ Vt_timing_debug2_t__03a__03aClkClass::__VnoInFunc_flip