https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202625
Most clang-tidy checks use a factory whose only state is the check constructor. Instantiating a separate templated factory class for every check emits duplicate virtual machinery and RTTI into clang-tidy. Store a constructor function pointer directly for stateless checks while retaining the existing factory-object path for checks that carry state. This preserves registration and construction behavior while allowing the compiler and linker to coalesce the shared factory implementation. On arm64 Release builds, the fully stripped standalone clang-tidy binary shrinks from 71,643,328 to 71,542,816 bytes, saving 100,512 bytes (0.140%). The stripped all-tools multicall binary shrinks from 147,944,136 to 147,861,144 bytes, saving 82,992 bytes. ClangTidyTests passes all 347 tests. The list-checks and custom-query-check lit tests pass, including the stateful factory path. The complete check listing and representative diagnostics are byte-identical before and after the change. LLVM has no dedicated benchmark for this path. Five hundred repeated list-check invocations show no measurable regression, and the all-check workload changed by 0.46%, within run-to-run noise. Work towards #202616 >From c76d043066e1dbf190f639ec794bb132a01acf68 Mon Sep 17 00:00:00 2001 From: David Zbarsky <[email protected]> Date: Mon, 8 Jun 2026 13:27:54 -0400 Subject: [PATCH] [clang-tidy] Store stateless check factories as function pointers Most clang-tidy checks use a factory whose only state is the check constructor. Instantiating a separate templated factory class for every check emits duplicate virtual machinery and RTTI into clang-tidy. Store a constructor function pointer directly for stateless checks while retaining the existing factory-object path for checks that carry state. This preserves registration and construction behavior while allowing the compiler and linker to coalesce the shared factory implementation. On arm64 Release builds, the fully stripped standalone clang-tidy binary shrinks from 71,643,328 to 71,542,816 bytes, saving 100,512 bytes (0.140%). The stripped all-tools multicall binary shrinks from 147,944,136 to 147,861,144 bytes, saving 82,992 bytes. ClangTidyTests passes all 347 tests. The list-checks and custom-query-check lit tests pass, including the stateful factory path. The complete check listing and representative diagnostics are byte-identical before and after the change. LLVM has no dedicated benchmark for this path. Five hundred repeated list-check invocations show no measurable regression, and the all-check workload changed by 0.46%, within run-to-run noise. --- .../clang-tidy/ClangTidyModule.cpp | 43 ++++++++++++++++++- .../clang-tidy/ClangTidyModule.h | 39 ++++++++++++++--- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/clang-tools-extra/clang-tidy/ClangTidyModule.cpp b/clang-tools-extra/clang-tidy/ClangTidyModule.cpp index 976e87dffb0bf..b599714fa1bb0 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyModule.cpp @@ -15,9 +15,50 @@ namespace clang::tidy { +ClangTidyCheckFactories::FactoryEntry::FactoryEntry( + CheckFactoryFunction Function) + : Function(Function) {} + +ClangTidyCheckFactories::FactoryEntry::FactoryEntry(CheckFactory Factory) + : Factory(std::make_unique<CheckFactory>(std::move(Factory))) {} + +ClangTidyCheckFactories::FactoryEntry::FactoryEntry(const FactoryEntry &Other) + : Function(Other.Function) { + if (Other.Factory) + Factory = std::make_unique<CheckFactory>(*Other.Factory); +} + +ClangTidyCheckFactories::FactoryEntry & +ClangTidyCheckFactories::FactoryEntry::operator=(const FactoryEntry &Other) { + if (this == &Other) + return *this; + Function = Other.Function; + Factory = + Other.Factory ? std::make_unique<CheckFactory>(*Other.Factory) : nullptr; + return *this; +} + +std::unique_ptr<ClangTidyCheck> +ClangTidyCheckFactories::FactoryEntry::operator()( + StringRef Name, ClangTidyContext *Context) const { + if (Function) + return Function(Name, Context); + return (*Factory)(Name, Context); +} + void ClangTidyCheckFactories::registerCheckFactory(StringRef Name, CheckFactory Factory) { - Factories.insert_or_assign(Name, std::move(Factory)); + Factories.insert_or_assign(Name, FactoryEntry(std::move(Factory))); +} + +void ClangTidyCheckFactories::registerCheckFactory( + StringRef Name, const FactoryEntry &Factory) { + Factories.insert_or_assign(Name, Factory); +} + +void ClangTidyCheckFactories::registerCheckFunction( + StringRef Name, CheckFactoryFunction Function) { + Factories.insert_or_assign(Name, FactoryEntry(Function)); } std::vector<std::unique_ptr<ClangTidyCheck>> diff --git a/clang-tools-extra/clang-tidy/ClangTidyModule.h b/clang-tools-extra/clang-tidy/ClangTidyModule.h index 3db92c2dab981..42ea647bce0e6 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyModule.h +++ b/clang-tools-extra/clang-tidy/ClangTidyModule.h @@ -29,11 +29,32 @@ class ClangTidyCheckFactories { public: using CheckFactory = std::function<std::unique_ptr<ClangTidyCheck>( StringRef Name, ClangTidyContext *Context)>; + using CheckFactoryFunction = std::unique_ptr<ClangTidyCheck> (*)( + StringRef Name, ClangTidyContext *Context); + + class FactoryEntry { + public: + FactoryEntry(CheckFactoryFunction Function); + FactoryEntry(CheckFactory Factory); + FactoryEntry(const FactoryEntry &Other); + FactoryEntry &operator=(const FactoryEntry &Other); + FactoryEntry(FactoryEntry &&) = default; + FactoryEntry &operator=(FactoryEntry &&) = default; + + std::unique_ptr<ClangTidyCheck> + operator()(StringRef Name, ClangTidyContext *Context) const; + + private: + CheckFactoryFunction Function = nullptr; + std::unique_ptr<CheckFactory> Factory; + }; + static_assert(sizeof(FactoryEntry) == 2 * sizeof(void *)); /// Registers check \p Factory with name \p Name. /// /// For all checks that have default constructors, use \c registerCheck. void registerCheckFactory(StringRef Name, CheckFactory Factory); + void registerCheckFactory(StringRef Name, const FactoryEntry &Factory); /// Registers the \c CheckType with the name \p Name. /// @@ -57,10 +78,7 @@ class ClangTidyCheckFactories { /// }; /// \endcode template <typename CheckType> void registerCheck(StringRef CheckName) { - registerCheckFactory(CheckName, - [](StringRef Name, ClangTidyContext *Context) { - return std::make_unique<CheckType>(Name, Context); - }); + registerCheckFunction(CheckName, &createCheck<CheckType>); } void eraseCheck(StringRef CheckName) { Factories.erase(CheckName); } @@ -73,12 +91,23 @@ class ClangTidyCheckFactories { std::vector<std::unique_ptr<ClangTidyCheck>> createChecksForLanguage(ClangTidyContext *Context) const; - using FactoryMap = llvm::StringMap<CheckFactory>; + using FactoryMap = llvm::StringMap<FactoryEntry>; FactoryMap::const_iterator begin() const { return Factories.begin(); } FactoryMap::const_iterator end() const { return Factories.end(); } bool empty() const { return Factories.empty(); } private: + template <typename CheckType> +#if defined(__clang__) + __attribute__((internal_linkage)) +#endif + static std::unique_ptr<ClangTidyCheck> + createCheck(StringRef Name, ClangTidyContext *Context) { + return std::make_unique<CheckType>(Name, Context); + } + + void registerCheckFunction(StringRef Name, CheckFactoryFunction Function); + FactoryMap Factories; }; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
