From c76c94ef16cc95a4ab84f406718cf7f4c567a5b3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 23 Jun 2026 04:22:35 +0100 Subject: [PATCH] Optimize additional expression patterns in V3Const (#7824) - Masking that returns known zero - More general Sel over Extend --- src/V3Const.cpp | 89 +++++++++++++++++++++++++++++++---- src/V3Number.cpp | 8 ++++ src/V3Number.h | 1 + test_regress/t/t_opt_const.py | 2 +- 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 739313ff8..203fef45a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -1417,6 +1417,45 @@ class ConstVisitor final : public VNVisitor { return false; } + bool matchMaskedZero(const AstAnd* nodep) { + // Turn masking of known zero bits into constant zero. Commonly appears after V3Expand. + const AstConst* const maskp = VN_AS(nodep->lhsp(), Const); + const AstNodeExpr* const rhsp = nodep->rhsp(); + const uint32_t msbP1 = maskp->num().mostSetBitP1(); + if (!msbP1) return false; // Don't rewrite, separate rule matches for this + const uint32_t msb = msbP1 - 1; + const uint32_t lsb = maskp->num().leastSetBitP1() - 1; + + if (const AstShiftL* const shiftp = VN_CAST(rhsp, ShiftL)) { + // 'a << S' forces the low S bits to zero + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() // + && (scp->num().toUInt() > msb); + } + } + if (const AstShiftR* const shiftp = VN_CAST(rhsp, ShiftR)) { + // 'a >> S' forces the high S bits to zero. Check against the width of the shifted + // operand, V3Expand can create shifts wider than their inputs + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() + && (lsb + scp->num().toUInt() + >= static_cast(shiftp->lhsp()->widthMin())); + } + } + if (const AstMul* const mulp = VN_CAST(rhsp, Mul)) { + // 'C * a' forces the low N bits to zero where 'C' has low zero bits + if (AstConst* const cp = VN_CAST(mulp->lhsp(), Const)) { + return cp->num().leastSetBitP1() > msb + 1; + } + } + if (const AstExtend* const extendp = VN_CAST(rhsp, Extend)) { + // Zero-extension forces the bits above the source width to zero + return lsb >= static_cast(extendp->lhsp()->width()); + } + + return false; + } + bool matchBitOpTree(AstNodeExpr* nodep) { if (nodep->widthMin() != 1) return false; if (!v3Global.opt.fConstBitOpTree()) return false; @@ -1564,16 +1603,46 @@ class ConstVisitor final : public VNVisitor { && static_cast(nodep->widthConst()) == nodep->fromp()->width()); } bool operandSelExtend(AstSel* nodep) { - // A pattern created by []'s after offsets have been removed - // SEL(EXTEND(any,width,...),(width-1),0) -> ... - // Since select's return unsigned, this is always an extend - // cppcheck-suppress constVariablePointer // children unlinked below + if (!m_doV) return false; AstExtend* const extendp = VN_CAST(nodep->fromp(), Extend); - if (!(m_doV && extendp && VN_IS(nodep->lsbp(), Const) && nodep->lsbConst() == 0 - && static_cast(nodep->widthConst()) == extendp->lhsp()->width())) - return false; - VL_DO_DANGLING(replaceWChild(nodep, extendp->lhsp()), nodep); - return true; + if (!extendp) return false; + AstConst* const lsbp = VN_CAST(nodep->lsbp(), Const); + if (!lsbp) return false; + const int width = nodep->widthConst(); + const int lsb = lsbp->toSInt(); + const int msb = lsb + width - 1; + AstNodeExpr* const lhsp = extendp->lhsp(); + if (!lhsp->isPure()) return false; + + // Selecting the entire extended expression, replace with that + if (lsb == 0 && msb == lhsp->width() - 1) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(lhsp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select entirely in the extended part - replace with zero + if (lsb >= lhsp->width()) { + replaceZero(nodep); + return true; + } + // Select entirely in the extended expression - replace with select from that + if (msb < lhsp->width()) { + lhsp->unlinkFrBack(); + lsbp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstSel{nodep->fileline(), lhsp, lsbp, width}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select straddles both sides, but is just a shorter extend + if (lsb == 0) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstExtend{nodep->fileline(), lhsp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + + return false; } bool operandSelBiLower(AstSel* nodep) { // SEL(ADD(a,b),(width-1),0) -> ADD(SEL(a),SEL(b)) @@ -4223,6 +4292,8 @@ class ConstVisitor final : public VNVisitor { // Zero on one side or the other TREEOP ("AstAdd {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstAnd {$lhsp.isZero, $rhsp, $rhsp.isPure}", "replaceZero(nodep)"); // Can't use replaceZeroChkPure as we make this pattern in ChkPure + // Masking that always yields zero + TREEOP ("AstAnd {$lhsp.castConst, matchMaskedZero(nodep)}", "replaceZeroChkPure(nodep, $rhsp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogAnd {$lhsp.isZero}", "replaceZero(nodep)"); TREEOP ("AstLogAnd{$lhsp.isZero, $rhsp}", "replaceZero(nodep)"); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 58bb2740b..a9c505359 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1308,6 +1308,14 @@ uint32_t V3Number::mostSetBitP1() const { } return 0; } + +uint32_t V3Number::leastSetBitP1() const { + for (int bit = 0; bit < width(); ++bit) { + if (!bitIs0(bit)) return bit + 1; + } + return 0; +} + //====================================================================== V3Number& V3Number::opBitsNonXZ(const V3Number& lhs) { // 0/1->1, X/Z->0 diff --git a/src/V3Number.h b/src/V3Number.h index df726e496..3791f2f19 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -728,6 +728,7 @@ public: uint32_t countBits(const V3Number& ctrl1, const V3Number& ctrl2, const V3Number& ctrl3) const; uint32_t countOnes() const; uint32_t mostSetBitP1() const; // Highest bit set + 1, e.g. for 16 return 5, for 0 return 0 + uint32_t leastSetBitP1() const; // Lowest bit set + 1, e.g. for 14 return 2, for 0 return 0 // Operators bool operator<(const V3Number& rhs) const { return isLtXZ(rhs); } diff --git a/test_regress/t/t_opt_const.py b/test_regress/t/t_opt_const.py index e4d50d945..fa2e1cfee 100755 --- a/test_regress/t/t_opt_const.py +++ b/test_regress/t/t_opt_const.py @@ -16,7 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test. test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 50) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 46) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes()