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.
This commit is contained in:
Geza Lore 2025-11-08 10:36:12 +00:00 committed by GitHub
parent 2fabf50801
commit 0f96bd0f4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 326 additions and 303 deletions

View File

@ -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 ""; }

View File

@ -101,24 +101,36 @@ class EmitCSyms final : EmitCBaseVisitorConst {
std::map<const std::string, std::vector<std::string>> 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<std::pair<size_t, bool>> m_usesVfinal; // Split file index + uses __Vfinal
std::vector<std::string> 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<std::string>& stmts, bool destroy);
void emitSymImp(AstNetlist* netlistp);
void emitDpiHdr();
void emitDpiImp();
void emitSplit(std::vector<std::string>& stmts, const std::string name, size_t max_stmts);
std::vector<std::string> getSymCtorStmts();
std::vector<std::string> getSymDtorStmts();
static size_t stmtCost(const std::string& stmt) {
if (stmt.empty()) return 0;
if (VString::startsWith(stmt, "/")) return 0;
return static_cast<size_t>(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<std::string>& 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<std::string> EmitCSyms::getSymCtorStmts() {
std::vector<std::string> 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<VlThreadPool*>(contextp->threadPoolp())}\n");
}
if (v3Global.opt.profExec()) {
puts(" , "
"__Vm_executionProfilerp{static_cast<VlExecutionProfiler*>(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<const ExecMTask&>(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");
add("// Setup scopes");
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;
}
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"
if (!varp->isParam()) {
stmt += ", &(";
stmt += varName;
stmt += "), false, ";
} else if (varp->vlEnumType() == "VLVT_STRING"
&& !VN_IS(varp->subDTypep(), UnpackArrayDType)) {
puts(", const_cast<void*>(static_cast<const void*>(");
puts(varName);
puts(".c_str())), ");
stmt += ", const_cast<void*>(static_cast<const void*>(";
stmt += varName;
stmt += ".c_str())), true, ";
} else {
puts(", const_cast<void*>(static_cast<const void*>(&(");
puts(varName);
puts("))), ");
}
} else {
puts(", &(");
puts(varName);
puts("), ");
stmt += ", const_cast<void*>(static_cast<const void*>(&(";
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);
}
}
closeSplit();
return stmts;
}
std::vector<std::string> EmitCSyms::getSymDtorStmts() {
std::vector<std::string> 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<std::string>& 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<std::string> ctorStmts = getSymCtorStmts();
std::vector<std::string> dtorStmts = getSymDtorStmts();
// Check if needs splitting and if so split into sub-functions
if (const size_t maxCost = static_cast<size_t>(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<VlThreadPool*>(contextp->threadPoolp())}\n");
}
if (v3Global.opt.profExec()) {
puts(" , __Vm_executionProfilerp{static_cast<VlExecutionProfiler*>(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");
}
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");
VL_DO_CLEAR(delete m_ofpBase, m_ofpBase = nullptr);
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");
}
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");
}
}
closeOutputFile();
}
//######################################################################

View File

@ -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():

View File

@ -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)

View File

@ -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": []},

View File

@ -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,

View File

@ -1782,7 +1782,7 @@
</creset>
</cfunc>
</package>
<cfile loc="a,0,0,0,0" name="obj_vlt/t_xml_debugcheck/Vt_xml_debugcheck__Syms.cpp"/>
<cfile loc="a,0,0,0,0" name="obj_vlt/t_xml_debugcheck/Vt_xml_debugcheck__Syms__Slow.cpp"/>
<cfile loc="a,0,0,0,0" name="obj_vlt/t_xml_debugcheck/Vt_xml_debugcheck__Syms.h"/>
<cfile loc="a,0,0,0,0" name="obj_vlt/t_xml_debugcheck/Vt_xml_debugcheck.h"/>
<cfile loc="a,0,0,0,0" name="obj_vlt/t_xml_debugcheck/Vt_xml_debugcheck.cpp"/>