https://github.com/serge-sans-paille created https://github.com/llvm/llvm-project/pull/185740
…d-bit Change std::bitset<N>(x).count() into std::popcount(x). >From 2fdf0382f89028411966040c6bd98de473e78bb6 Mon Sep 17 00:00:00 2001 From: serge-sans-paille <[email protected]> Date: Tue, 10 Mar 2026 18:04:30 +0100 Subject: [PATCH] [clang-tidy] Detect std::popcount opportunity within modernize.use-std-bit Change std::bitset<N>(x).count() into std::popcount(x). --- .../clang-tidy/modernize/UseStdBitCheck.cpp | 68 ++++++++++--- .../checks/modernize/use-std-bit.rst | 1 + .../checkers/modernize/use-std-bit.cpp | 99 +++++++++++++++---- 3 files changed, 137 insertions(+), 31 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp index ff43f707a867b..cbc7301e6519f 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp @@ -72,7 +72,20 @@ void UseStdBitCheck::registerMatchers(MatchFinder *Finder) { LogicalNot(BitwiseAnd( BoundDeclRef("v"), Sub(BoundDeclRef("v"), integerLiteral(equals(1)))))) - .bind("expr"), + .bind("has_one_bit_expr"), + this); + + // Computing popcount with following pattern: + // std::bitset<N>(val).count() + Finder->addMatcher( + cxxMemberCallExpr( + argumentCountIs(0), + callee(cxxMethodDecl( + hasName("count"), + ofClass(cxxRecordDecl(hasName("bitset"), isInStdNamespace())))), + on(cxxConstructExpr( + hasArgument(0, expr(hasType(isUnsignedInteger())).bind("v"))))) + .bind("popcount_expr"), this); } @@ -90,19 +103,46 @@ void UseStdBitCheck::check(const MatchFinder::MatchResult &Result) { const ASTContext &Context = *Result.Context; const SourceManager &Source = Context.getSourceManager(); - const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v"); - const auto *MatchedExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr"); - - auto Diag = - diag(MatchedExpr->getBeginLoc(), "use 'std::has_one_bit' instead"); - if (auto R = MatchedExpr->getSourceRange(); - !R.getBegin().isMacroID() && !R.getEnd().isMacroID()) { - Diag << FixItHint::CreateReplacement( - MatchedExpr->getSourceRange(), - ("std::has_one_bit(" + MatchedVarDecl->getName() + ")").str()) - << IncludeInserter.createIncludeInsertion( - Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>"); + if (const auto *MatchedExpr = + Result.Nodes.getNodeAs<BinaryOperator>("expr")) { + const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v"); + + auto Diag = + diag(MatchedExpr->getBeginLoc(), "use 'std::has_one_bit' instead"); + if (auto R = MatchedExpr->getSourceRange(); + !R.getBegin().isMacroID() && !R.getEnd().isMacroID()) { + Diag << FixItHint::CreateReplacement( + MatchedExpr->getSourceRange(), + ("std::has_one_bit(" + MatchedVarDecl->getName() + ")").str()) + << IncludeInserter.createIncludeInsertion( + Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>"); + } else if (const auto *MatchedExpr = + Result.Nodes.getNodeAs<CXXMemberCallExpr>("popcount_expr")) { + const auto *BitsetInstantiatedDecl = + cast<ClassTemplateSpecializationDecl>(MatchedExpr->getRecordDecl()); + const llvm::APSInt BitsetSize = + BitsetInstantiatedDecl->getTemplateArgs()[0].getAsIntegral(); + const auto *MatchedArg = Result.Nodes.getNodeAs<Expr>("v"); + const uint64_t MatchedVarSize = + Context.getTypeSize(MatchedArg->getType()); + if (BitsetSize >= MatchedVarSize) { + MatchedArg->dump(); + auto Diag = + diag(MatchedExpr->getBeginLoc(), "use 'std::popcount' instead"); + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + MatchedArg->getEndLoc().getLocWithOffset(1), + MatchedExpr->getRParenLoc().getLocWithOffset(-1))) + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange( + MatchedExpr->getBeginLoc(), + MatchedArg->getBeginLoc().getLocWithOffset(-1)), + "std::popcount(") + << IncludeInserter.createIncludeInsertion( + Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>"); + } + } else { + llvm_unreachable(); + } } -} } // namespace clang::tidy::modernize diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst index 87d102d1f6ff8..26ef1b0841654 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst @@ -14,4 +14,5 @@ Expression Replacement ``x && !(x & (x - 1))`` ``std::has_one_bit(x)`` ``(x != 0) && !(x & (x - 1))`` ``std::has_one_bit(x)`` ``(x > 0) && !(x & (x - 1))`` ``std::has_one_bit(x)`` +``std::bitset<N>(x).count()`` ``std::popcount(x)`` ============================== ======================= diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp index 51f9d3485fc26..5e2898c01b259 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp @@ -1,87 +1,90 @@ // RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-std-bit %t // CHECK-FIXES: #include <bit> -unsigned bithacks(unsigned x) { +/* + * has_one_bit pattern + */ +unsigned has_one_bit_bithack(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return x && !(x & (x - 1)); } -unsigned long bithacks(unsigned long x) { +unsigned long has_one_bit_bithack(unsigned long x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return x && !(x & (x - 1)); } -unsigned short bithacks(unsigned short x) { +unsigned short has_one_bit_bithack(unsigned short x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return x && !(x & (x - 1)); } -unsigned bithacks_perm(unsigned x) { +unsigned has_one_bit_bithack_perm(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return x && !((x - 1) & (x)); } -unsigned bithacks_otherperm(unsigned x) { +unsigned has_one_bit_bithack_otherperm(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return !((x - 1) & (x)) && x; } -unsigned bithacks_variant_neq(unsigned x) { +unsigned has_one_bit_bithack_variant_neq(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (x != 0) && !(x & (x - 1)); } -unsigned bithacks_variant_neq_perm(unsigned x) { +unsigned has_one_bit_bithack_variant_neq_perm(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (x != 0) && !(x & (x - 1)); } -unsigned bithacks_variant_gt(unsigned x) { +unsigned has_one_bit_bithack_variant_gt(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (x > 0) && !(x & (x - 1)); } -unsigned bithacks_variant_gte(unsigned x) { +unsigned has_one_bit_bithacks_variant_gte(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (x >= 1) && !(x & (x - 1)); } -unsigned bithacks_variant_lt(unsigned x) { +unsigned has_one_bit_bithacks_variant_lt(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (0 < x) && !(x & (x - 1)); } -unsigned bithacks_variant_lte(unsigned x) { +unsigned has_one_bit_bithacks_variant_lte(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (1 <= x) && !(x & (x - 1)); } -unsigned bithacks_variant_gt_perm(unsigned x) { +unsigned has_one_bit_bithack_variant_gt_perm(unsigned x) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // CHECK-FIXES: return std::has_one_bit(x); return (x > 0) && !(x & (x - 1)); } #define HAS_ONE_BIT v && !(v & (v - 1)) -unsigned bithacks_macro(unsigned v) { +unsigned has_one_bit_bithack_macro(unsigned v) { // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead [modernize-use-std-bit] // No fixes, it comes from macro expansion. return HAS_ONE_BIT; } /* - * Invalid patterns + * Invalid has_one_bit patterns */ struct integer_like { integer_like operator!() const; @@ -90,7 +93,7 @@ struct integer_like { friend integer_like operator-(integer_like, unsigned); }; -unsigned invalid_bithacks(integer_like w, unsigned x, signed y, unsigned z) { +unsigned invalid_has_one_bit_bithack(integer_like w, unsigned x, signed y, unsigned z) { bool patterns[] = { // non commutative operators x && !(x & (1 - x)), @@ -111,7 +114,69 @@ unsigned invalid_bithacks(integer_like w, unsigned x, signed y, unsigned z) { } template <class T> -T bithacks_generic(T x) { - // substitution only valid for some instantiation of bithacks_generic +T has_one_bit_bithack_generic(T x) { + // substitution only valid for some instantiation of has_one_bit_bithack_generic return x && !(x & (x - 1)); } + +/* + * popcount pattern + */ +namespace std { +using size_t = decltype(sizeof(0)); +template<size_t N> class bitset { + public: + bitset(unsigned long); + size_t count() const; +}; +} + +unsigned popcount_bitset(unsigned x) { + // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead [modernize-use-std-bit] + // CHECK-FIXES: return std::popcount(x); + return std::bitset<sizeof(x) * 8>(x).count(); +} + +unsigned popcount_bitset_short(unsigned short x) { + // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead [modernize-use-std-bit] + // CHECK-FIXES: return std::popcount(x); + return std::bitset<sizeof(x) * 8>(x).count(); +} + +unsigned popcount_bitset_larger(unsigned x) { + // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead [modernize-use-std-bit] + // CHECK-FIXES: return std::popcount(x); + return std::bitset<sizeof(x) * 16>(x).count(); +} + +unsigned popcount_bitset_uniform_init(unsigned x) { + // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead [modernize-use-std-bit] + // CHECK-FIXES: return std::popcount(x); + return std::bitset<sizeof(x) * 16>{x}.count(); +} + +unsigned popcount_bitset_expr(unsigned x) { + // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead [modernize-use-std-bit] + // CHECK-FIXES: return std::popcount(x + 1); + return std::bitset<sizeof(x) * 8>{x + 1}.count(); +} + +/* + * Invalid has_one_bit patterns + */ +template<std::size_t N> class bitset { + public: + bitset(unsigned long); + std::size_t count() const; +}; + +unsigned invalid_popcount_bitset(unsigned x, signed y) { + std::size_t patterns[] = { + // truncating bitset + std::bitset<1>{x}.count(), + // unsupported types + std::bitset<sizeof(y) * 8>(y).count(), + bitset<sizeof(x) * 8>{x}.count(), + }; +} + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
