Reduce peak memory use of V3Subst (#4687)

V3Subst is currently the pass responsible for peak memory usage. This
patch saves ~16% of peak memory on OpenTitan.

2 changes:
- It is actually safe to delete the substituted expressions immediately,
  but this is the lesser contribution
- More importantly, we only ever substitute STMTTEMP variables, which
  are always defined within the same CFunc, so we can limit the scope of
  the optimization to CFunc. This allows us to reclaim the SubstVarEntry
  structures at the end of every CFunc, rather than at the end of the
  whole pass, which gives us most of the memory savings.

Generated output is identical
This commit is contained in:
Geza Lore 2023-11-12 16:01:07 +00:00 committed by GitHub
parent b07ffb3c78
commit 1c0af6c7bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 24 additions and 22 deletions

View File

@ -219,15 +219,15 @@ class SubstVisitor final : public VNVisitor {
private:
// NODE STATE
// Passed to SubstUseVisitor
// AstVar::user1p -> SubstVar* for usage var, 0=not set yet
// AstVar::user1p -> SubstVar* for usage var, 0=not set yet. Only under CFunc.
// AstVar::user2 -> int step number for last assignment, 0=not set yet
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
// STATE
std::vector<SubstVarEntry*> m_entryps; // Nodes to delete when we are finished
std::deque<SubstVarEntry> m_entries; // Nodes to delete when we are finished
int m_ops = 0; // Number of operators on assign rhs
int m_assignStep = 0; // Assignment number to determine var lifetime
const AstCFunc* m_funcp = nullptr; // Current function we are under
VDouble0 m_statSubsts; // Statistic tracking
enum {
@ -237,21 +237,18 @@ private:
// METHODS
SubstVarEntry* getEntryp(AstVarRef* nodep) {
if (!nodep->varp()->user1p()) {
SubstVarEntry* const entryp = new SubstVarEntry{nodep->varp()};
m_entryps.push_back(entryp);
nodep->varp()->user1p(entryp);
return entryp;
} else {
SubstVarEntry* const entryp
= reinterpret_cast<SubstVarEntry*>(nodep->varp()->user1p());
return entryp;
AstVar* const varp = nodep->varp();
if (!varp->user1p()) {
m_entries.emplace_back(varp);
varp->user1p(&m_entries.back());
}
return varp->user1u().to<SubstVarEntry*>();
}
bool isSubstVar(AstVar* nodep) { return nodep->isStatementTemp() && !nodep->noSubst(); }
// VISITORS
void visit(AstNodeAssign* nodep) override {
if (!m_funcp) return;
VL_RESTORER(m_ops);
m_ops = 0;
m_assignStep++;
@ -295,10 +292,11 @@ private:
}
if (debug() > 5) newp->dumpTree("- w_new: ");
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
++m_statSubsts;
}
void visit(AstWordSel* nodep) override {
if (!m_funcp) return;
iterate(nodep->rhsp());
AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef);
const AstConst* const constp = VN_CAST(nodep->rhsp(), Const);
@ -324,6 +322,7 @@ private:
}
}
void visit(AstVarRef* nodep) override {
if (!m_funcp) return;
// Any variable
if (nodep->access().isWriteOrRW()) {
m_assignStep++;
@ -353,13 +352,19 @@ private:
}
void visit(AstVar*) override {}
void visit(AstConst*) override {}
void visit(AstModule* nodep) override {
++m_ops;
if (!nodep->isSubstOptimizable()) m_ops = SUBST_MAX_OPS_NA;
void visit(AstCFunc* nodep) override {
UASSERT_OBJ(!m_funcp, nodep, "Should not nest");
UASSERT_OBJ(m_entries.empty(), nodep, "References outside functions");
VL_RESTORER(m_funcp);
m_funcp = nodep;
const VNUser1InUse m_inuser1;
iterateChildren(nodep);
// Reduce peak memory usage by reclaiming the edited AstNodes
doDeletes();
for (SubstVarEntry& ip : m_entries) ip.deleteUnusedAssign();
m_entries.clear();
}
void visit(AstNode* nodep) override {
++m_ops;
if (!nodep->isSubstOptimizable()) m_ops = SUBST_MAX_OPS_NA;
@ -371,10 +376,7 @@ public:
explicit SubstVisitor(AstNode* nodep) { iterate(nodep); }
~SubstVisitor() override {
V3Stats::addStat("Optimizations, Substituted temps", m_statSubsts);
for (SubstVarEntry* ip : m_entryps) {
ip->deleteUnusedAssign();
delete ip;
}
UASSERT(m_entries.empty(), "References outside functions");
}
};