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.
This commit is contained in:
Geza Lore 2026-02-01 17:08:49 +00:00 committed by GitHub
parent 7ca113a84f
commit d3f608058f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 71 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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