From d3f608058f9b5f0bd1a1394e8ae59ceb53b14d54 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 1 Feb 2026 17:08:49 +0000 Subject: [PATCH] Optimize expanded constant pool words (#6979) Re-inline ConstPool entries in V3Subst that have been expanded into word-wise accessed by V3Expand. This enables downstream constant folding on the word-wise expressions. As V3Subst now understands ConstPool entries, we can also omit expanding straight assignments with a ConstPool entry on the RHS. This allows the C++ compiler to see the memcpy directly. --- src/V3Expand.cpp | 15 +++++++++++ src/V3Subst.cpp | 43 +++++++++++++++++++++++++------ test_regress/t/t_opt_subst.py | 8 +++++- test_regress/t/t_opt_subst.v | 11 ++++++++ test_regress/t/t_opt_subst_off.py | 2 +- test_regress/t/t_reloop_cam.py | 4 +-- 6 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index ed6a0eb98..0eb724ea5 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -71,7 +71,10 @@ public: class ExpandVisitor final : public VNVisitor { // NODE STATE // AstNode::user1() -> bool. Processed + // AstNode::user2() -> See ExpandOkVisitor + // AstVar::user3() -> bool. Is a constant pool variable const VNUser1InUse m_inuser1; + const VNUser3InUse m_inuser3; // STATE - for current visit position (use VL_RESTORER) AstNode* m_stmtp = nullptr; // Current statement @@ -313,6 +316,9 @@ class ExpandVisitor final : public VNVisitor { //-------- Uniops bool expandWide(AstNodeAssign* nodep, AstVarRef* rhsp) { UINFO(8, " Wordize ASSIGN(VARREF) " << nodep); + // Special case: do not expand assignment of constant pool variables. + // V3Subst undestands these directly. + if (rhsp->varp()->user3()) return false; if (!doExpandWide(nodep)) return false; for (int w = 0; w < nodep->widthWords(); ++w) { addWordAssign(nodep, w, newAstWordSelClone(rhsp, w)); @@ -429,6 +435,15 @@ class ExpandVisitor final : public VNVisitor { } // VISITORS + void visit(AstNetlist* nodep) override { + // Mark constant pool variables + for (AstNode* np = nodep->constPoolp()->modp()->stmtsp(); np; np = np->nextp()) { + if (VN_IS(np, Var)) np->user3(true); + } + + iterateAndNextNull(nodep->modulesp()); + } + void visit(AstCFunc* nodep) override { VL_RESTORER(m_funcp); VL_RESTORER(m_nTmps); diff --git a/src/V3Subst.cpp b/src/V3Subst.cpp index 3d7afd93b..a97d21d72 100644 --- a/src/V3Subst.cpp +++ b/src/V3Subst.cpp @@ -218,6 +218,8 @@ AstNodeExpr* SubstVarEntry::substRecord(SubstVarEntry::Record& record) { class SubstVisitor final : public VNVisitor { // NODE STATE - only Under AstCFunc // AstVar::user1p -> SubstVarEntry* for assignment tracking. Also used by SubstValidVisitor + // AstVar::user2 -> bool. Is a constant pool variable + const VNUser2InUse m_user2InUse; // STATE std::deque m_entries; // Storage for SubstVarEntry instances @@ -230,6 +232,7 @@ class SubstVisitor final : public VNVisitor { size_t m_nWholeSubstituted = 0; // Number of whole variables substituted size_t m_nWordAssignDeleted = 0; // Number of word assignments deleted size_t m_nWholeAssignDeleted = 0; // Number of whole variable assignments deleted + size_t m_nConstWordsReinlined = 0; // Number of constant words substituted static constexpr uint32_t SUBST_MAX_OPS_SUBST = 30; // Maximum number of ops to substitute in static constexpr uint32_t SUBST_MAX_OPS_NA = 9999; // Not allowed to substitute @@ -260,7 +263,7 @@ class SubstVisitor final : public VNVisitor { bool isSubstitutable(AstVar* nodep) { return nodep->isStatementTemp() && !nodep->noSubst(); } void substitute(AstNode* nodep, AstNodeExpr* substp) { - AstNodeExpr* newp = substp->cloneTreePure(true); + AstNodeExpr* newp = substp->backp() ? substp->cloneTreePure(true) : substp; if (!nodep->isQuad() && newp->isQuad()) { newp = new AstCCast{newp->fileline(), newp, nodep}; } @@ -270,6 +273,16 @@ class SubstVisitor final : public VNVisitor { } // VISITORS + + void visit(AstNetlist* nodep) override { + // Mark constant pool variables + for (AstNode* np = nodep->constPoolp()->modp()->stmtsp(); np; np = np->nextp()) { + if (VN_IS(np, Var)) np->user2(true); + } + + iterateAndNextNull(nodep->modulesp()); + } + void visit(AstCFunc* nodep) override { UASSERT_OBJ(!m_funcp, nodep, "Should not nest"); UASSERT_OBJ(m_entries.empty(), nodep, "Should not visit outside functions"); @@ -357,8 +370,21 @@ class SubstVisitor final : public VNVisitor { return; } - // Otherwise it's a read, substitute it if possible + // Otherwise it's a read, UASSERT_OBJ(refp->access().isReadOnly(), nodep, "Invalid access"); + + // If it's a constant pool variable, substiute with the constant word + AstVar* const varp = refp->varp(); + if (varp->user2()) { + AstConst* const constp = VN_AS(varp->valuep(), Const); + const uint32_t value = constp->num().edataWord(word); + FileLine* const flp = nodep->fileline(); + ++m_nConstWordsReinlined; + substitute(nodep, new AstConst{flp, AstConst::SizedEData{}, value}); + return; + } + + // Substitute other variables if possible if (isSubstitutable(refp->varp())) { if (AstNodeExpr* const substp = entry.substWord(word)) { ++m_nWordSubstituted; @@ -425,14 +451,15 @@ class SubstVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit SubstVisitor(AstNode* nodep) { iterate(nodep); } + explicit SubstVisitor(AstNetlist* nodep) { iterate(nodep); } ~SubstVisitor() override { - V3Stats::addStat("Optimizations, Substituted temps", m_nSubst); - V3Stats::addStat("Optimizations, Whole variable assignments deleted", + V3Stats::addStat("Optimizations, Subst, Substituted temps", m_nSubst); + V3Stats::addStat("Optimizations, Subst, Constant words reinlined", m_nConstWordsReinlined); + V3Stats::addStat("Optimizations, Subst, Whole variable assignments deleted", m_nWholeAssignDeleted); - V3Stats::addStat("Optimizations, Whole variables substituted", m_nWholeSubstituted); - V3Stats::addStat("Optimizations, Word assignments deleted", m_nWordAssignDeleted); - V3Stats::addStat("Optimizations, Words substituted", m_nWordSubstituted); + V3Stats::addStat("Optimizations, Subst, Whole variables substituted", m_nWholeSubstituted); + V3Stats::addStat("Optimizations, Subst, Word assignments deleted", m_nWordAssignDeleted); + V3Stats::addStat("Optimizations, Subst, Words substituted", m_nWordSubstituted); UASSERT(m_entries.empty(), "Should not visit outside functions"); } }; diff --git a/test_regress/t/t_opt_subst.py b/test_regress/t/t_opt_subst.py index b113ff3c7..b013d8319 100755 --- a/test_regress/t/t_opt_subst.py +++ b/test_regress/t/t_opt_subst.py @@ -14,7 +14,13 @@ test.scenarios('simulator_st') test.compile(verilator_flags2=["--stats"]) if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, Substituted temps\s+(\d+)', 43) + test.file_grep(test.stats, r'Optimizations, Subst, Constant words reinlined\s+(\d+)', 156) + test.file_grep(test.stats, r'Optimizations, Subst, Substituted temps\s+(\d+)', 225) + test.file_grep(test.stats, r'Optimizations, Subst, Whole variable assignments deleted\s+(\d+)', + 1) + test.file_grep(test.stats, r'Optimizations, Subst, Whole variables substituted\s+(\d+)', 1) + test.file_grep(test.stats, r'Optimizations, Subst, Word assignments deleted\s+(\d+)', 68) + test.file_grep(test.stats, r'Optimizations, Subst, Words substituted\s+(\d+)', 68) test.execute() diff --git a/test_regress/t/t_opt_subst.v b/test_regress/t/t_opt_subst.v index 3cc2913b6..f2b761190 100644 --- a/test_regress/t/t_opt_subst.v +++ b/test_regress/t/t_opt_subst.v @@ -12,6 +12,7 @@ module t ( integer i; reg [94:0] w95; + reg [399:0] w400; integer cyc = 0; @@ -24,6 +25,7 @@ module t ( if (cyc == 0) begin // Setup w95 = {95{1'b1}}; + w400 = '1; end else if (cyc == 1) begin if (w95++ != {95{1'b1}}) $stop; @@ -34,6 +36,15 @@ module t ( if (w95 != {95{1'b0}}) $stop; if (--w95 != {95{1'b1}}) $stop; if (w95 != {95{1'b1}}) $stop; + + if (w400++ != {400{1'b1}}) $stop; + if (w400 != {400{1'b0}}) $stop; + if (w400-- != {400{1'b0}}) $stop; + if (w400 != {400{1'b1}}) $stop; + if (++w400 != {400{1'b0}}) $stop; + if (w400 != {400{1'b0}}) $stop; + if (--w400 != {400{1'b1}}) $stop; + if (w400 != {400{1'b1}}) $stop; end else if (cyc == 99) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_opt_subst_off.py b/test_regress/t/t_opt_subst_off.py index 43eb853fc..c59b32156 100755 --- a/test_regress/t/t_opt_subst_off.py +++ b/test_regress/t/t_opt_subst_off.py @@ -15,6 +15,6 @@ test.top_filename = "t/t_opt_life.v" test.compile(verilator_flags2=['--stats', '-fno-subst', '-fno-subst-const']) if test.vlt_all: - test.file_grep_not(test.stats, r'Optimizations, Substituted temps\s+(\d+)') + test.file_grep_not(test.stats, r'Optimizations, Subst,') test.passes() diff --git a/test_regress/t/t_reloop_cam.py b/test_regress/t/t_reloop_cam.py index ddde69d77..3333f0da8 100755 --- a/test_regress/t/t_reloop_cam.py +++ b/test_regress/t/t_reloop_cam.py @@ -18,7 +18,7 @@ test.compile(verilator_flags2=[ test.execute() if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, Reloop iterations\s+(\d+)', 768) - test.file_grep(test.stats, r'Optimizations, Reloops\s+(\d+)', 3) + test.file_grep(test.stats, r'Optimizations, Reloop iterations\s+(\d+)', 512) + test.file_grep(test.stats, r'Optimizations, Reloops\s+(\d+)', 2) test.passes()