diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt @@ -4,6 +4,7 @@ ) add_clang_library(clangTidyPerformanceModule + EnumSizeCheck.cpp FasterStringFindCheck.cpp ForRangeCopyCheck.cpp ImplicitConversionInLoopCheck.cpp diff --git a/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.h b/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.h @@ -0,0 +1,36 @@ +//===--- EnumSizeCheck.h - clang-tidy ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_ENUMSIZECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_ENUMSIZECHECK_H + +#include "../ClangTidyCheck.h" +#include + +namespace clang::tidy::performance { + +/// Finds `enum` type definitions that could use smaller integral type as a +/// base. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance/enum-size.html +class EnumSizeCheck : public ClangTidyCheck { +public: + EnumSizeCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + +private: + std::string EnumIgnoreRegexp; +}; + +} // namespace clang::tidy::performance + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_ENUMSIZECHECK_H diff --git a/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp b/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/EnumSizeCheck.cpp @@ -0,0 +1,129 @@ +//===--- EnumSizeCheck.cpp - clang-tidy -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EnumSizeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include +#include +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::performance { + +namespace { + +const std::uint64_t Min8 = + std::imaxabs(std::numeric_limits::min()); +const std::uint64_t Max8 = std::numeric_limits::max(); +const std::uint64_t Min16 = + std::imaxabs(std::numeric_limits::min()); +const std::uint64_t Max16 = std::numeric_limits::max(); +const std::uint64_t Min32 = + std::imaxabs(std::numeric_limits::min()); +const std::uint64_t Max32 = std::numeric_limits::max(); + +std::pair +getNewType(std::size_t Size, std::uint64_t Min, std::uint64_t Max) noexcept { + if (Min) { + if (Min <= Min8 && Max <= Max8) { + return {"std::int8_t", sizeof(std::int8_t)}; + } + + if (Min <= Min16 && Max <= Max16 && Size > sizeof(std::int16_t)) { + return {"std::int16_t", sizeof(std::int16_t)}; + } + + if (Min <= Min32 && Max <= Max32 && Size > sizeof(std::int32_t)) { + return {"std::int32_t", sizeof(std::int32_t)}; + } + + return {}; + } + + if (Max) { + if (Max <= std::numeric_limits::max()) { + return {"std::uint8_t", sizeof(std::uint8_t)}; + } + + if (Max <= std::numeric_limits::max() && + Size > sizeof(std::uint16_t)) { + return {"std::uint16_t", sizeof(std::uint16_t)}; + } + + if (Max <= std::numeric_limits::max() && + Size > sizeof(std::uint32_t)) { + return {"std::uint32_t", sizeof(std::uint32_t)}; + } + + return {}; + } + + // Zero case + return {"std::uint8_t", sizeof(std::uint8_t)}; +} + +} // namespace + +EnumSizeCheck::EnumSizeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + EnumIgnoreRegexp(Options.get("EnumIgnoreRegexp", "^$")) {} + +void EnumSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "EnumIgnoreRegexp", EnumIgnoreRegexp); +} + +bool EnumSizeCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus11; +} + +void EnumSizeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(enumDecl(unless(isExpansionInSystemHeader()), + isDefinition(), + unless(matchesName(EnumIgnoreRegexp))) + .bind("e"), + this); +} + +void EnumSizeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = Result.Nodes.getNodeAs("e"); + const QualType BaseType = MatchedDecl->getIntegerType().getCanonicalType(); + if (!BaseType->isIntegerType()) + return; + + const std::uint32_t Size = Result.Context->getTypeSize(BaseType) / 8U; + if (1U == Size) + return; + + std::uint64_t MinV = 0U; + std::uint64_t MaxV = 0U; + + for (const auto &It : MatchedDecl->enumerators()) { + const llvm::APSInt &InitVal = It->getInitVal(); + if ((InitVal.isUnsigned() || InitVal.isNonNegative())) { + MaxV = std::max(MaxV, InitVal.getZExtValue()); + } else { + MinV = std::max(MinV, InitVal.abs().getZExtValue()); + } + } + + auto NewType = getNewType(Size, MinV, MaxV); + if (!NewType.first || Size <= NewType.second) + return; + + diag(MatchedDecl->getLocation(), "enum %0 derive from %1 of size %2 bytes, " + "derive from '%3' to reduce it size to %4") + << MatchedDecl << MatchedDecl->getIntegerType() << Size << NewType.first + << NewType.second; +} + +} // namespace clang::tidy::performance diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp --- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -9,6 +9,7 @@ #include "../ClangTidy.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "EnumSizeCheck.h" #include "FasterStringFindCheck.h" #include "ForRangeCopyCheck.h" #include "ImplicitConversionInLoopCheck.h" @@ -31,6 +32,7 @@ class PerformanceModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("performance-enum-size"); CheckFactories.registerCheck( "performance-faster-string-find"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -116,6 +116,11 @@ Checks that all implicit and explicit inline functions in header files are tagged with the ``LIBC_INLINE`` macro. +- New :doc:`performance-enum-size + ` check. + + Finds ``enum`` type definitions that could use smaller integral type as a base. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -310,6 +310,7 @@ `objc-super-self `_, "Yes" `openmp-exception-escape `_, `openmp-use-default-none `_, + `performance-enum-size `_, `performance-faster-string-find `_, "Yes" `performance-for-range-copy `_, "Yes" `performance-implicit-conversion-in-loop `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/performance/enum-size.rst @@ -0,0 +1,45 @@ +.. title:: clang-tidy - performance-enum-size + +performance-enum-size +===================== + +Finds ``enum`` type definitions that could use smaller integral type as a base. +By default all, ``enum`` and ``enum`` classes use as base, integral types of +minimum size equal 32 bit. Since C++11 there is possibility to specify custom +base type, and therefor reduce size of ``enum`` from four or eight bytes to one +or two. + +Requires C++11 or above. + +Example +------- + +.. code-block:: c++ + + enum EnumType // : std::int16_t + { + NEGATIVE_10 = -10, + POSITIVE_100 = 100, + POSITIVE_1000 = 1000 + }; + + class Values + { + public: + EnumType first; + EnumType second; + }; + + +``EnumType`` will use as base ``int``, but could easily use ``std::int16_t`` +(``short``). After that operation size of ``Values`` would reduce from +8 bytes to 4 bytes. This will also impact alignment of ``Values`` class by +reducing it from 4 bytes to 2 bytes. + +Options +------- + +.. option:: EnumIgnoreRegexp + + Regexp used to ignore definitions of enum types that match regexp. + The default is `^$`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/enum-size.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/enum-size.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/enum-size.cpp @@ -0,0 +1,94 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s performance-enum-size %t -- \ +// RUN: -config="{CheckOptions: [{key: performance-enum-size.EnumIgnoreRegexp, value: '^::IgnoredEnum$'}]}" + +namespace std +{ +using uint8_t = unsigned char; +using int8_t = signed char; +using uint16_t = unsigned short; +using int16_t = signed short; +using uint32_t = unsigned int; +using int32_t = signed int; +} + +enum class Value +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: enum 'Value' derive from 'int' of size 4 bytes, derive from 'std::uint8_t' to reduce it size to 1 [performance-enum-size] +{ + supported +}; + + +enum class EnumClass : std::int16_t +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: enum 'EnumClass' derive from 'std::int16_t' (aka 'short') of size 2 bytes, derive from 'std::uint8_t' to reduce it size to 1 [performance-enum-size] +{ + supported +}; + +enum EnumWithType : std::uint16_t +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'EnumWithType' derive from 'std::uint16_t' (aka 'unsigned short') of size 2 bytes, derive from 'std::uint8_t' to reduce it size to 1 [performance-enum-size] +{ + supported, + supported2 +}; + +enum EnumWithNegative +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'EnumWithNegative' derive from 'int' of size 4 bytes, derive from 'std::int8_t' to reduce it size to 1 [performance-enum-size] +{ + s1 = -128, + s2 = -100, + s3 = 100, + s4 = 127 +}; + +enum EnumThatCanBeReducedTo2Bytes +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'EnumThatCanBeReducedTo2Bytes' derive from 'int' of size 4 bytes, derive from 'std::int16_t' to reduce it size to 2 [performance-enum-size] +{ + a1 = -128, + a2 = 0x6EEE +}; + +enum EnumOnlyNegative +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'EnumOnlyNegative' derive from 'int' of size 4 bytes, derive from 'std::int8_t' to reduce it size to 1 [performance-enum-size] +{ + b1 = -125, + b2 = -50, + b3 = -10 +}; + +enum CorrectU8 : std::uint8_t +{ + c01 = 10, + c02 = 11 +}; + +enum CorrectU16 : std::uint16_t +{ + c11 = 10, + c12 = 0xFFFF +}; + +constexpr int getValue() +{ + return 256; +} + + +enum CalculatedDueToUnknown1 : unsigned int +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'CalculatedDueToUnknown1' derive from 'unsigned int' of size 4 bytes, derive from 'std::uint16_t' to reduce it size to 2 [performance-enum-size] +{ + c21 = 10, + c22 = getValue() +}; + +enum CalculatedDueToUnknown2 : unsigned int +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: enum 'CalculatedDueToUnknown2' derive from 'unsigned int' of size 4 bytes, derive from 'std::uint16_t' to reduce it size to 2 [performance-enum-size] +{ + c31 = 10, + c32 = c31 + 246 +}; + +enum class IgnoredEnum : std::uint32_t +{ + unused1 = 1, + unused2 = 2 +};