Optimize additional expression patterns in V3Const (#7824)

- Masking that returns known zero
- More general Sel over Extend
This commit is contained in:
Geza Lore 2026-06-23 04:22:35 +01:00 committed by GitHub
parent 756bcf5742
commit c76c94ef16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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