From 0f96bd0f4d67376fa6f2f3b84cbcd137fb229d55 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 8 Nov 2025 10:36:12 +0000 Subject: [PATCH] Fix splitting of Syms constructor/destructor bodies (#6662) Splitting of the Syms constructor/destructor were a bit arbitrarily enforced with some parts splitable, while others not. There was also an issue that even if the constructor and destructor bodies were split, we would still end up with both in the same file that was double the size of the intended split limit. To fix, first all statements required in the Syms constructor and destructor are gathered into a vector, then if the total number of statements required for both is bigger than the split limit, the implementations are split into sub-functions, one per file, as before, ensuring that none of the functions are bigger than the split limit. Also add __Slow suffix to the names of the files. Patch 2 of 3 to fix long compile times of the Syms module in some scenarios. --- src/V3Ast.h | 1 + src/V3EmitCSyms.cpp | 610 +++++++++++----------- test_regress/t/t_flag_csplit.py | 4 +- test_regress/t/t_flag_csplit_groups.py | 6 +- test_regress/t/t_json_only_debugcheck.out | 2 +- test_regress/t/t_timing_split.py | 4 +- test_regress/t/t_xml_debugcheck.out | 2 +- 7 files changed, 326 insertions(+), 303 deletions(-) diff --git a/src/V3Ast.h b/src/V3Ast.h index 65054e378..ed1bc52f0 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -578,6 +578,7 @@ public: static constexpr int INSTR_COUNT_STR = 100; // String ops static constexpr int INSTR_COUNT_TIME = INSTR_COUNT_CALL + 5; // Determine simulation time static constexpr int INSTR_COUNT_PLI = 20; // PLI routines + static constexpr int INSTR_COUNT_SYM = 5; // Syms ctor/dtor statements // ACCESSORS virtual string name() const VL_MT_STABLE { return ""; } diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index cff85daaf..e2d90c12b 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -101,24 +101,36 @@ class EmitCSyms final : EmitCBaseVisitorConst { std::map> m_vpiScopeHierarchy; int m_coverBins = 0; // Coverage bin number const bool m_dpiHdrOnly; // Only emit the DPI header - int m_numStmts = 0; // Number of statements output - size_t m_funcNum = 0; // CFunc split function number - V3OutCFile* m_ofpBase = nullptr; // Base (not split) C file - AstCFile* m_ofpBaseFile = nullptr; // Base (not split) AstCFile - std::vector> m_usesVfinal; // Split file index + uses __Vfinal + std::vector m_splitFuncNames; // Split file names VDouble0 m_statVarScopeBytes; // Statistic tracking const std::string m_symsFileBase = v3Global.opt.makeDir() + "/" + symClassName(); // METHODS + void openOutputFile(const std::string fileName) { + V3OutCFile* const fp = optSystemC() ? new V3OutScFile{fileName} : new V3OutCFile{fileName}; + AstCFile* const cfilep = newCFile(fileName, true /*slow*/, true /*source*/); + cfilep->support(true); + setOutputFile(fp, cfilep); + } + void emitSymHdr(); - void checkSplit(bool usesVfinal); - void closeSplit(); void emitSymImpPreamble(); - void emitScopeHier(bool destroy); - void emitSymImp(); + void emitScopeHier(std::vector& stmts, bool destroy); + void emitSymImp(AstNetlist* netlistp); void emitDpiHdr(); void emitDpiImp(); + void emitSplit(std::vector& stmts, const std::string name, size_t max_stmts); + + std::vector getSymCtorStmts(); + std::vector getSymDtorStmts(); + + static size_t stmtCost(const std::string& stmt) { + if (stmt.empty()) return 0; + if (VString::startsWith(stmt, "/")) return 0; + return static_cast(AstNode::INSTR_COUNT_SYM); + } + static void nameCheck(AstNode* nodep) { // Prevent GCC compile time error; name check all things that reach C++ code if (nodep->name().empty()) return; @@ -293,7 +305,7 @@ class EmitCSyms final : EmitCBaseVisitorConst { // Output if (!m_dpiHdrOnly) { // Must emit implementation first to determine number of splits - emitSymImp(); + emitSymImp(nodep); emitSymHdr(); } if (v3Global.dpi()) { @@ -537,11 +549,7 @@ void EmitCSyms::emitSymHdr() { + "* modelp);\n"); puts("~" + symClassName() + "();\n"); - for (const auto& i : m_usesVfinal) { - puts("void " + symClassName() + "_" + std::to_string(i.first) + "("); - if (i.second) puts("int __Vfinal"); - puts(");\n"); - } + for (const std::string& funcName : m_splitFuncNames) { puts("void " + funcName + "();\n"); } puts("\n// METHODS\n"); puts("const char* name() { return TOP.vlNamep; }\n"); @@ -587,54 +595,6 @@ void EmitCSyms::emitSymHdr() { closeOutputFile(); } -void EmitCSyms::closeSplit() { - UASSERT(ofp(), "There should be an output file"); - // If we are in the base file, nothign to do - if (ofp() == m_ofpBase) return; - - // Close sub-function definition in split file and close the file - puts("}\n"); - closeOutputFile(); - - // Resume output to the base file - setOutputFile(m_ofpBase, m_ofpBaseFile); -} - -void EmitCSyms::checkSplit(bool usesVfinal) { - UASSERT(ofp(), "There should be an output file"); - if (!v3Global.opt.outputSplitCFuncs()) return; - if (m_numStmts < v3Global.opt.outputSplitCFuncs()) return; - - // Splitting file, so using parallel build. - v3Global.useParallelBuild(true); - - // If alredy in a split file, close it - if (ofp() != m_ofpBase) closeSplit(); - - // New function umber - const size_t funcNum = ++m_funcNum; - - // Call the sub-function in the base file - puts(symClassName() + "_" + std::to_string(funcNum) + "("); - if (usesVfinal) puts("__Vfinal"); - puts(");\n"); - - // Create new split file - m_usesVfinal.emplace_back(funcNum, usesVfinal); - const std::string filename = m_symsFileBase + "__" + std::to_string(funcNum) + ".cpp"; - AstCFile* const cfilep = newCFile(filename, true /*slow*/, true /*source*/); - cfilep->support(true); - V3OutCFile* const ofilep = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; - setOutputFile(ofilep, cfilep); - m_numStmts = 0; - - // Emit header and open sub function definition in the split file - emitSymImpPreamble(); - puts("void " + symClassName() + "::" + symClassName() + "_" + std::to_string(funcNum) + "("); - if (usesVfinal) puts("int __Vfinal"); - puts(") {\n"); -} - void EmitCSyms::emitSymImpPreamble() { ofp()->putsHeader(); puts("// DESCR" @@ -661,12 +621,16 @@ void EmitCSyms::emitSymImpPreamble() { if (needsNewLine) puts("\n"); } -void EmitCSyms::emitScopeHier(bool destroy) { +void EmitCSyms::emitScopeHier(std::vector& stmts, bool destroy) { if (!v3Global.opt.vpi()) return; - const std::string verb = destroy ? "Tear down" : "Set up"; + if (destroy) { + stmts.emplace_back("// Tear down scope hierarchy"); + } else { + stmts.emplace_back("// Set up scope hierarchy"); + } + const std::string method = destroy ? "remove" : "add"; - puts("\n// " + verb + " scope hierarchy\n"); for (const auto& itpair : m_scopeNames) { if (itpair.first == "TOP") continue; const ScopeData& sd = itpair.second; @@ -674,280 +638,152 @@ void EmitCSyms::emitScopeHier(bool destroy) { const std::string& scopeType = sd.m_type; if (name.find('.') != string::npos) continue; if (scopeType != "SCOPE_MODULE" && scopeType != "SCOPE_PACKAGE") continue; - putns(sd.m_nodep, - "__Vhier." + method + "(0, &" + protect("__Vscope_" + sd.m_symName) + ");\n"); + const std::string id = protect("__Vscope_" + sd.m_symName); + stmts.emplace_back("__Vhier." + method + "(0, &" + id + ");"); } - for (const auto& itpair : m_vpiScopeHierarchy) { - const std::string fromname = scopeSymString(itpair.first); - const ScopeData& from = m_scopeNames.at(fromname); + const std::string fromName = scopeSymString(itpair.first); + const std::string fromId = protect("__Vscope_" + m_scopeNames.at(fromName).m_symName); for (const std::string& name : itpair.second) { - const std::string toname = scopeSymString(name); - const ScopeData& to = m_scopeNames.at(toname); - puts("__Vhier." + method + "("); - puts("&" + protect("__Vscope_" + from.m_symName) + ", "); - puts("&" + protect("__Vscope_" + to.m_symName) + ");\n"); + const std::string toName = scopeSymString(name); + const std::string toId = protect("__Vscope_" + m_scopeNames.at(toName).m_symName); + stmts.emplace_back("__Vhier." + method + "(&" + fromId + ", &" + toId + ");"); } } - puts("\n"); } -void EmitCSyms::emitSymImp() { - UINFO(6, __FUNCTION__ << ": "); - const std::string filename = m_symsFileBase + ".cpp"; - m_ofpBaseFile = newCFile(filename, true /*slow*/, true /*source*/); - m_ofpBaseFile->support(true); - m_ofpBase = optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename}; - setOutputFile(m_ofpBase, m_ofpBaseFile); +std::vector EmitCSyms::getSymCtorStmts() { + std::vector stmts; - emitSymImpPreamble(); - - if (v3Global.opt.savable()) { - for (const bool& de : {false, true}) { - const std::string classname = de ? "VerilatedDeserialize" : "VerilatedSerialize"; - const std::string funcname = protect(de ? "__Vdeserialize" : "__Vserialize"); - const std::string op = de ? ">>" : "<<"; - // NOLINTNEXTLINE(performance-inefficient-string-concatenation) - puts("void " + symClassName() + "::" + funcname + "(" + classname + "& os) {\n"); - puts("// Internal state\n"); - if (v3Global.opt.trace()) puts("os" + op + "__Vm_activity;\n"); - puts("os " + op + " __Vm_didInit;\n"); - puts("// Module instance state\n"); - for (const ScopeModPair& itpair : m_scopes) { - const AstScope* const scopep = itpair.first; - puts(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." - + funcname + "(os);\n"); - } - puts("}\n"); - } - puts("\n"); - } - - puts("// FUNCTIONS\n"); - - // Destructor - puts(symClassName() + "::~" + symClassName() + "()\n"); - puts("{\n"); - emitScopeHier(true); - if (v3Global.needTraceDumper()) { - puts("#ifdef VM_TRACE\n"); - puts("if (__Vm_dumping) _traceDumpClose();\n"); - puts("#endif // VM_TRACE\n"); - } - if (v3Global.opt.profPgo()) { - puts("_vm_pgoProfiler.write(\"" + topClassName() - + "\", _vm_contextp__->profVltFilename());\n"); - } - puts("// Tear down sub module instances\n"); - for (const ScopeModPair& itpair : vlstd::reverse_view(m_scopes)) { - const AstScope* const scopep = itpair.first; - const AstNodeModule* const modp = itpair.second; - if (modp->isTop()) continue; - putns(scopep, protect(scopep->nameDotless())); - puts(".dtor();\n"); - ++m_numStmts; - } - puts("}\n"); - - if (v3Global.needTraceDumper()) { - if (!optSystemC()) { - puts("\nvoid " + symClassName() + "::_traceDump() {\n"); - // Caller checked for __Vm_dumperp non-nullptr - puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); - puts("__Vm_dumperp->dump(VL_TIME_Q());\n"); - puts("}\n"); - } - - puts("\nvoid " + symClassName() + "::_traceDumpOpen() {\n"); - puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); - puts("if (VL_UNLIKELY(!__Vm_dumperp)) {\n"); - puts("__Vm_dumperp = new " + v3Global.opt.traceClassLang() + "();\n"); - puts("__Vm_modelp->trace(__Vm_dumperp, 0, 0);\n"); - puts("const std::string dumpfile = _vm_contextp__->dumpfileCheck();\n"); - puts("__Vm_dumperp->open(dumpfile.c_str());\n"); - puts("__Vm_dumping = true;\n"); - puts("}\n"); - puts("}\n"); - - puts("\nvoid " + symClassName() + "::_traceDumpClose() {\n"); - puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); - puts("__Vm_dumping = false;\n"); - puts("VL_DO_CLEAR(delete __Vm_dumperp, __Vm_dumperp = nullptr);\n"); - puts("}\n"); - } - puts("\n"); - - // Constructor - puts(symClassName() + "::" + symClassName() - + "(VerilatedContext* contextp, const char* namep, " + topClassName() + "* modelp)\n"); - puts(" : VerilatedSyms{contextp}\n"); - puts(" // Setup internal state of the Syms class\n"); - puts(" , __Vm_modelp{modelp}\n"); - - if (v3Global.opt.mtasks()) { - puts(" , __Vm_threadPoolp{static_cast(contextp->threadPoolp())}\n"); - } - - if (v3Global.opt.profExec()) { - puts(" , " - "__Vm_executionProfilerp{static_cast(contextp->" - "enableExecutionProfiler(&VlExecutionProfiler::construct))}\n"); - } - if (v3Global.opt.profPgo() && !v3Global.opt.libCreate().empty()) { - puts(" , _vm_pgoProfiler{" + std::to_string(v3Global.currentHierBlockCost()) + "}\n"); - } - - puts(" // Setup top module instance\n"); - for (const ScopeModPair& itpair : m_scopes) { - const AstScope* const scopep = itpair.first; - const AstNodeModule* const modp = itpair.second; - if (!modp->isTop()) continue; - puts(" , "); - putns(scopep, protect(scopep->nameDotless())); - puts("{this, namep}\n"); - ++m_numStmts; - break; - } - puts("{\n"); + const auto add = [&stmts](const std::string& stmt) { stmts.emplace_back(stmt); }; { - puts("// Check resources\n"); uint64_t stackSize = V3StackCount::count(v3Global.rootp()); if (v3Global.opt.debugStackCheck()) stackSize += 1024 * 1024 * 1024; V3Stats::addStat("Size prediction, Stack (bytes)", stackSize); - puts("Verilated::stackCheck(" + std::to_string(stackSize) + ");\n"); // TODO: 'm_statVarScopeBytes' is always 0, AstVarScope doesn't reach here (V3Descope) V3Stats::addStat("Size prediction, Heap, from Var Scopes (bytes)", m_statVarScopeBytes); V3Stats::addStat(V3Stats::STAT_MODEL_SIZE, stackSize + m_statVarScopeBytes); + + add("// Check resources"); + add("Verilated::stackCheck(" + std::to_string(stackSize) + ");"); } - puts("// Setup sub module instances\n"); + add("// Setup sub module instances"); for (const ScopeModPair& itpair : m_scopes) { const AstScope* const scopep = itpair.first; const AstNodeModule* const modp = itpair.second; if (modp->isTop()) continue; - putns(scopep, protect(scopep->nameDotless())); - puts(".ctor(this, "); - putsQuoted(VIdProtect::protectWordsIf(scopep->prettyName(), scopep->protect())); - puts(");\n"); - ++m_numStmts; + const std::string name = V3OutFormatter::quoteNameControls( + VIdProtect::protectWordsIf(scopep->prettyName(), scopep->protect())); + add(protect(scopep->nameDotless()) + ".ctor(this, \"" + name + "\");"); } if (v3Global.opt.profPgo()) { - puts("// Configure profiling for PGO\n"); + add("// Configure profiling for PGO\n"); if (!v3Global.opt.hierChild()) { - puts("_vm_pgoProfiler.writeHeader(_vm_contextp__->profVltFilename());\n"); + add("_vm_pgoProfiler.writeHeader(_vm_contextp__->profVltFilename());"); } if (v3Global.opt.mtasks()) { v3Global.rootp()->topModulep()->foreach([&](const AstExecGraph* execGraphp) { for (const V3GraphVertex& vtx : execGraphp->depGraphp()->vertices()) { const ExecMTask& mt = static_cast(vtx); - puts("_vm_pgoProfiler.addCounter(" + std::to_string(mt.id()) + ", \"" - + mt.hashName() + "\");\n"); + add("_vm_pgoProfiler.addCounter(" + std::to_string(mt.id()) + ", \"" + + mt.hashName() + "\");"); } }); } } - puts("// Configure time unit / time precision\n"); + add("// Configure time unit / time precision"); if (!v3Global.rootp()->timeunit().isNone()) { - puts("_vm_contextp__->timeunit("); - puts(std::to_string(v3Global.rootp()->timeunit().powerOfTen())); - puts(");\n"); + const std::string unit = std::to_string(v3Global.rootp()->timeunit().powerOfTen()); + add("_vm_contextp__->timeunit(" + unit + ");"); } if (!v3Global.rootp()->timeprecision().isNone()) { - puts("_vm_contextp__->timeprecision("); - puts(std::to_string(v3Global.rootp()->timeprecision().powerOfTen())); - puts(");\n"); + const std::string prec = std::to_string(v3Global.rootp()->timeprecision().powerOfTen()); + add("_vm_contextp__->timeprecision(" + prec + ");"); } - puts("// Setup each module's pointers to their submodules\n"); + add("// Setup each module's pointers to their submodules"); for (const auto& i : m_scopes) { const AstScope* const scopep = i.first; const AstNodeModule* const modp = i.second; const AstScope* const abovep = scopep->aboveScopep(); if (!abovep) continue; - checkSplit(false); const std::string protName = VIdProtect::protectWordsIf(scopep->name(), scopep->protect()); + std::string stmt; if (VN_IS(modp, ClassPackage)) { // ClassPackage modules seem to be a bit out of place, so hard code... - putns(scopep, "TOP"); + stmt += "TOP"; } else { - putns(scopep, VIdProtect::protectIf(abovep->nameDotless(), abovep->protect())); + stmt += VIdProtect::protectIf(abovep->nameDotless(), abovep->protect()); } - puts("."); - puts(protName.substr(protName.rfind('.') + 1)); - puts(" = &"); - puts(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + ";\n"); - ++m_numStmts; + stmt += "."; + stmt += protName.substr(protName.rfind('.') + 1); + stmt += " = &"; + stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + ";"; + add(stmt); } - puts("// Setup each module's pointer back to symbol table (for public functions)\n"); + add("// Setup each module's pointer back to symbol table (for public functions)"); for (const ScopeModPair& i : m_scopes) { const AstScope* const scopep = i.first; AstNodeModule* const modp = i.second; - checkSplit(false); // first is used by AstCoverDecl's call to __vlCoverInsert const bool first = !modp->user1(); modp->user1(true); - putns(scopep, VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." - + protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");\n"); - ++m_numStmts; + add(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + + protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");"); } - if (!m_scopeNames.empty()) { // Setup scope names - puts("// Setup scopes\n"); - for (const auto& itpair : m_scopeNames) { - checkSplit(false); - const ScopeData& sd = itpair.second; - putns(sd.m_nodep, protect("__Vscope_" + sd.m_symName) + ".configure(this, name(), "); - putsQuoted(VIdProtect::protectWordsIf(sd.m_prettyName, true)); - puts(", "); - putsQuoted(protect(scopeDecodeIdentifier(sd.m_prettyName))); - puts(", "); - putsQuoted(sd.m_defName); - puts(", "); - puts(std::to_string(sd.m_timeunit)); - puts(", VerilatedScope::" + sd.m_type + ");\n"); - ++m_numStmts; - } + add("// Setup scopes"); + for (const auto& itpair : m_scopeNames) { + const ScopeData& sd = itpair.second; + std::string stmt; + stmt += protect("__Vscope_" + sd.m_symName) + ".configure(this, name(), \""; + stmt += V3OutFormatter::quoteNameControls( + VIdProtect::protectWordsIf(sd.m_prettyName, true)); + stmt += "\", \""; + stmt += V3OutFormatter::quoteNameControls(protect(scopeDecodeIdentifier(sd.m_prettyName))); + stmt += "\", \""; + stmt += V3OutFormatter::quoteNameControls(sd.m_defName); + stmt += "\", "; + stmt += std::to_string(sd.m_timeunit); + stmt += ", VerilatedScope::" + sd.m_type + ");"; + add(stmt); } - emitScopeHier(false); + emitScopeHier(stmts, false); - // Everything past here is in the __Vfinal loop, so start a new split file if needed - closeSplit(); + if (!v3Global.dpi()) return stmts; - if (v3Global.dpi()) { - - puts("// Setup export functions\n"); - puts("for (int __Vfinal = 0; __Vfinal < 2; ++__Vfinal) {\n"); + for (const std::string vfinal : {"0", "1"}) { + add("// Setup export functions - final: " + vfinal); for (const auto& itpair : m_scopeFuncs) { const ScopeFuncData& sfd = itpair.second; const AstScopeName* const scopep = sfd.m_scopep; const AstCFunc* const funcp = sfd.m_cfuncp; const AstNodeModule* const modp = sfd.m_modp; - if (!funcp->dpiExportImpl()) continue; - checkSplit(true); - putns(scopep, - protect("__Vscope_" + scopep->scopeSymName()) + ".exportInsert(__Vfinal, "); - putsQuoted(funcp->cname()); // Not protected - user asked for import/export - puts(", (void*)(&"); - puts(EmitCUtil::prefixNameProtect(modp)); - puts("__"); - puts(funcp->nameProtect()); - puts("));\n"); - ++m_numStmts; + std::string stmt; + stmt += protect("__Vscope_" + scopep->scopeSymName()) + ".exportInsert("; + stmt += vfinal + ", \""; + // Not protected - user asked for import/export + stmt += V3OutFormatter::quoteNameControls(funcp->cname()); + stmt += "\", (void*)(&"; + stmt += EmitCUtil::prefixNameProtect(modp); + stmt += "__"; + stmt += funcp->nameProtect(); + stmt += "));"; + add(stmt); } // It would be less code if each module inserted its own variables. // Someday. For now public isn't common. for (const auto& itpair : m_scopeVars) { - checkSplit(true); const ScopeVarData& svd = itpair.second; - const AstScope* const scopep = svd.m_scopep; const AstVar* const varp = svd.m_varp; int pdim = 0; @@ -981,52 +817,238 @@ void EmitCSyms::emitSymImp() { } } - putns(scopep, protect("__Vscope_" + svd.m_scopeName)); - putns(varp, ".varInsert(__Vfinal,"); - putsQuoted(protect(svd.m_varBasePretty)); + std::string stmt; + stmt += protect("__Vscope_" + svd.m_scopeName) + ".varInsert("; + stmt += vfinal + ", \""; + stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + protect(varp->name()); - if (varp->isParam()) { - if (varp->vlEnumType() == "VLVT_STRING" - && !VN_IS(varp->subDTypep(), UnpackArrayDType)) { - puts(", const_cast(static_cast("); - puts(varName); - puts(".c_str())), "); - } else { - puts(", const_cast(static_cast(&("); - puts(varName); - puts("))), "); - } + if (!varp->isParam()) { + stmt += ", &("; + stmt += varName; + stmt += "), false, "; + } else if (varp->vlEnumType() == "VLVT_STRING" + && !VN_IS(varp->subDTypep(), UnpackArrayDType)) { + stmt += ", const_cast(static_cast("; + stmt += varName; + stmt += ".c_str())), true, "; } else { - puts(", &("); - puts(varName); - puts("), "); + stmt += ", const_cast(static_cast(&("; + stmt += varName; + stmt += "))), true, "; } - puts(varp->isParam() ? "true" : "false"); - puts(", "); - puts(varp->vlEnumType()); // VLVT_UINT32 etc - puts(","); - puts(varp->vlEnumDir()); // VLVD_IN etc - puts(","); - puts(std::to_string(udim)); - puts(","); - puts(std::to_string(pdim)); - puts(bounds); - puts(");\n"); - ++m_numStmts; + stmt += varp->vlEnumType(); // VLVT_UINT32 etc + stmt += ", "; + stmt += varp->vlEnumDir(); // VLVD_IN etc + stmt += ", "; + stmt += std::to_string(udim); + stmt += ", "; + stmt += std::to_string(pdim); + stmt += bounds; + stmt += ");"; + add(stmt); + } + } + + return stmts; +} + +std::vector EmitCSyms::getSymDtorStmts() { + std::vector stmts; + + const auto add = [&stmts](const std::string& stmt) { stmts.emplace_back(stmt); }; + + emitScopeHier(stmts, true); + if (v3Global.needTraceDumper()) add("if (__Vm_dumping) _traceDumpClose();"); + if (v3Global.opt.profPgo()) { + add("_vm_pgoProfiler.write(\"" + topClassName() + + "\", _vm_contextp__->profVltFilename());"); + } + add("// Tear down sub module instances"); + for (const ScopeModPair& itpair : vlstd::reverse_view(m_scopes)) { + const AstScope* const scopep = itpair.first; + const AstNodeModule* const modp = itpair.second; + if (modp->isTop()) continue; + add(protect(scopep->nameDotless()) + ".dtor();"); + } + return stmts; +} + +void EmitCSyms::emitSplit(std::vector& stmts, const std::string name, + size_t maxCost) { + const std::string baseName = m_symsFileBase + "__" + name; + size_t nSubFunctions = 0; + // Reduce into a balanced tree of sub-function calls until we end up with a single statement + while (stmts.size() > 1) { + size_t nSplits = 0; + size_t nStmts = stmts.size(); + for (size_t splitStart = 0, splitEnd = 0; splitStart < nStmts; splitStart = splitEnd) { + // Gather up at at most 'maxCost' worth of statements in this split, + // but always at least 2 (if less than 2, the reduction makes no + // progress and the loop will not terminate). + size_t cost = 0; + while (((cost < maxCost) || (splitEnd - splitStart < 2)) && splitEnd < nStmts) { + cost += stmtCost(stmts[splitEnd++]); + } + UASSERT(splitStart < splitEnd, "Empty split"); + + // Create new sub-function and emit current range of statementss + const std::string nStr = std::to_string(nSubFunctions++); + // Name of sub-function we are emitting now + const std::string funcName = symClassName() + "__" + name + "__" + nStr; + m_splitFuncNames.emplace_back(funcName); + // Open split file + openOutputFile(baseName + "__" + nStr + "__Slow.cpp"); + // Emit header + emitSymImpPreamble(); + // Open sub-function definition in the split file + puts("void " + symClassName() + "::" + funcName + "() {\n"); + + // Emit statements + for (size_t j = splitStart; j < splitEnd; ++j) { + m_ofp->putsNoTracking(" "); + m_ofp->putsNoTracking(stmts[j]); + m_ofp->putsNoTracking("\n"); + } + + // Close sub-function + puts("}\n"); + // Close split file + closeOutputFile(); + + // Replace statements with a call to the sub-function + stmts[nSplits++] = funcName + "();"; + } + // The statements at the front are now the calls to the sub-functions, drop the rest + stmts.resize(nSplits); + } +} + +void EmitCSyms::emitSymImp(AstNetlist* netlistp) { + UINFO(6, __FUNCTION__ << ": "); + + // Get the body of the constructor and destructor + std::vector ctorStmts = getSymCtorStmts(); + std::vector dtorStmts = getSymDtorStmts(); + + // Check if needs splitting and if so split into sub-functions + if (const size_t maxCost = static_cast(v3Global.opt.outputSplitCFuncs())) { + size_t totalCost = 200; // Starting from 200 to consider all other contents in main file + if (totalCost <= maxCost) { + for (const std::string& stmt : ctorStmts) { + totalCost += stmtCost(stmt); + if (totalCost > maxCost) break; + } + } + if (totalCost <= maxCost) { + for (const std::string& stmt : dtorStmts) { + totalCost += stmtCost(stmt); + if (totalCost > maxCost) break; + } + } + // Split them if needed + if (totalCost > maxCost) { + v3Global.useParallelBuild(true); // Splitting files, so using parallel build. + emitSplit(ctorStmts, "ctor", maxCost); + emitSplit(dtorStmts, "dtor", maxCost); + } + } + + openOutputFile(m_symsFileBase + "__Slow.cpp"); + emitSymImpPreamble(); + + // Constructor + const std::string ctorArgs + = "VerilatedContext* contextp, const char* namep, " + topClassName() + "* modelp"; + puts(symClassName() + "::" + symClassName() + "(" + ctorArgs + ")\n"); + puts(" : VerilatedSyms{contextp}\n"); + puts(" // Setup internal state of the Syms class\n"); + puts(" , __Vm_modelp{modelp}\n"); + if (v3Global.opt.mtasks()) { + puts(" , __Vm_threadPoolp{static_cast(contextp->threadPoolp())}\n"); + } + if (v3Global.opt.profExec()) { + puts(" , __Vm_executionProfilerp{static_cast(contextp->" + "enableExecutionProfiler(&VlExecutionProfiler::construct))}\n"); + } + if (v3Global.opt.profPgo() && !v3Global.opt.libCreate().empty()) { + puts(" , _vm_pgoProfiler{" + std::to_string(v3Global.currentHierBlockCost()) + "}\n"); + } + { + const AstScope* const scopep = netlistp->topScopep()->scopep(); + puts(" // Setup top module instance\n"); + puts(" , " + protect(scopep->nameDotless()) + "{this, namep}\n"); + } + puts("{\n"); + for (const std::string& stmt : ctorStmts) { + m_ofp->putsNoTracking(" "); + m_ofp->putsNoTracking(stmt); + m_ofp->putsNoTracking("\n"); + } + puts("}\n"); + + // Destructor + puts("\n" + symClassName() + "::~" + symClassName() + "() {\n"); + for (const std::string& stmt : dtorStmts) { + m_ofp->putsNoTracking(" "); + m_ofp->putsNoTracking(stmt); + m_ofp->putsNoTracking("\n"); + } + puts("}\n"); + + // Methods + if (v3Global.needTraceDumper()) { + if (!optSystemC()) { + puts("\nvoid " + symClassName() + "::_traceDump() {\n"); + puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); + // Caller checked for __Vm_dumperp non-nullptr + puts("__Vm_dumperp->dump(VL_TIME_Q());\n"); + puts("}\n"); } - closeSplit(); + puts("\nvoid " + symClassName() + "::_traceDumpOpen() {\n"); + puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); + puts("if (VL_UNLIKELY(!__Vm_dumperp)) {\n"); + puts("__Vm_dumperp = new " + v3Global.opt.traceClassLang() + "();\n"); + puts("__Vm_modelp->trace(__Vm_dumperp, 0, 0);\n"); + puts("const std::string dumpfile = _vm_contextp__->dumpfileCheck();\n"); + puts("__Vm_dumperp->open(dumpfile.c_str());\n"); + puts("__Vm_dumping = true;\n"); + puts("}\n"); + puts("}\n"); + + puts("\nvoid " + symClassName() + "::_traceDumpClose() {\n"); + puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n"); + puts("__Vm_dumping = false;\n"); + puts("VL_DO_CLEAR(delete __Vm_dumperp, __Vm_dumperp = nullptr);\n"); puts("}\n"); } - puts("}\n"); + if (v3Global.opt.savable()) { + for (const bool& de : {false, true}) { + const std::string classname = de ? "VerilatedDeserialize" : "VerilatedSerialize"; + const std::string funcname = protect(de ? "__Vdeserialize" : "__Vserialize"); + const std::string op = de ? ">>" : "<<"; + puts("\nvoid " + symClassName() + "::" + funcname + "(" + classname + "& os) {\n"); + puts("// Internal state\n"); + if (v3Global.opt.trace()) puts("os" + op + "__Vm_activity;\n"); + puts("os " + op + " __Vm_didInit;\n"); + puts("// Module instance state\n"); + for (const ScopeModPair& itpair : m_scopes) { + const AstScope* const scopep = itpair.first; + const std::string scopeName + = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()); + puts(scopeName + "." + funcname + "(os);\n"); + } + puts("}\n"); + } + } - VL_DO_CLEAR(delete m_ofpBase, m_ofpBase = nullptr); + closeOutputFile(); } //###################################################################### diff --git a/test_regress/t/t_flag_csplit.py b/test_regress/t/t_flag_csplit.py index a63e3ac2a..11a0ae93c 100755 --- a/test_regress/t/t_flag_csplit.py +++ b/test_regress/t/t_flag_csplit.py @@ -16,14 +16,14 @@ def check_splits(): got1 = False gotSyms1 = False for filename in test.glob_some(test.obj_dir + "/*.cpp"): - if re.search(r'Syms__1', filename): + if re.search(r'Syms__.*__1', filename): gotSyms1 = True elif re.search(r'__1', filename): got1 = True if not got1: test.error("No __1 split file found") if not gotSyms1: - test.error("No Syms__1 split file found") + test.error("No Syms__*__1 split file found") def check_no_all_file(): diff --git a/test_regress/t/t_flag_csplit_groups.py b/test_regress/t/t_flag_csplit_groups.py index 0bfb1eb83..33ba50c14 100755 --- a/test_regress/t/t_flag_csplit_groups.py +++ b/test_regress/t/t_flag_csplit_groups.py @@ -18,14 +18,14 @@ def check_splits(): got1 = False gotSyms1 = False for filename in test.glob_some(test.obj_dir + "/*.cpp"): - if re.search(r'Syms__1', filename): + if re.search(r'Syms__.*__1', filename): gotSyms1 = True elif re.search(r'__1', filename): got1 = True if not got1: test.error("No __1 split file found") if not gotSyms1: - test.error("No Syms__1 split file found") + test.error("No Syms__*__1 split file found") def check_no_all_file(): @@ -125,7 +125,7 @@ test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_clas test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2") # Check combine count -test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (239 if test.vltmt else 222)) +test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (270 if test.vltmt else 253)) test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_FAST + (\d+)', 2) test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_SLOW + (\d+)', 2) diff --git a/test_regress/t/t_json_only_debugcheck.out b/test_regress/t/t_json_only_debugcheck.out index e73ebe86d..98c4306c8 100644 --- a/test_regress/t/t_json_only_debugcheck.out +++ b/test_regress/t/t_json_only_debugcheck.out @@ -2923,7 +2923,7 @@ ]} ], "filesp": [ - {"type":"CFILE","name":"obj_vlt/t_json_only_debugcheck/Vt_json_only_debugcheck__Syms.cpp","addr":"(ZQB)","loc":"a,0:0,0:0","source":true,"slow":true,"tblockp": []}, + {"type":"CFILE","name":"obj_vlt/t_json_only_debugcheck/Vt_json_only_debugcheck__Syms__Slow.cpp","addr":"(ZQB)","loc":"a,0:0,0:0","source":true,"slow":true,"tblockp": []}, {"type":"CFILE","name":"obj_vlt/t_json_only_debugcheck/Vt_json_only_debugcheck__Syms.h","addr":"(ARB)","loc":"a,0:0,0:0","source":false,"slow":true,"tblockp": []}, {"type":"CFILE","name":"obj_vlt/t_json_only_debugcheck/Vt_json_only_debugcheck.h","addr":"(BRB)","loc":"a,0:0,0:0","source":false,"slow":false,"tblockp": []}, {"type":"CFILE","name":"obj_vlt/t_json_only_debugcheck/Vt_json_only_debugcheck.cpp","addr":"(CRB)","loc":"a,0:0,0:0","source":true,"slow":false,"tblockp": []}, diff --git a/test_regress/t/t_timing_split.py b/test_regress/t/t_timing_split.py index 54cfda002..15b97428a 100755 --- a/test_regress/t/t_timing_split.py +++ b/test_regress/t/t_timing_split.py @@ -16,14 +16,14 @@ def check_splits(): got1 = False gotSyms1 = False for filename in test.glob_some(test.obj_dir + "/*.cpp"): - if re.search(r'Syms__1', filename): + if re.search(r'Syms__.*__1', filename): gotSyms1 = True elif re.search(r'__1', filename): got1 = True if not got1: test.error("No __1 split file found") if not gotSyms1: - test.error("No Syms__1 split file found") + test.error("No Syms__*__1 split file found") test.compile(timing_loop=True, diff --git a/test_regress/t/t_xml_debugcheck.out b/test_regress/t/t_xml_debugcheck.out index 3d90fa4b7..9e5469979 100644 --- a/test_regress/t/t_xml_debugcheck.out +++ b/test_regress/t/t_xml_debugcheck.out @@ -1782,7 +1782,7 @@ - +