https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202627
StaticInitializationCycleCheck used separate dynamic recursive AST visitors to build its graph and collect uses. Each subclass emitted a complete DynamicRecursiveASTVisitor vtable. Reuse VarUseGraphBuilder in an explicit collection mode. This preserves the existing traversal filters while removing VarUseCollector and its vtable. A static assertion keeps the merged visitor at five pointer widths. On arm64 macOS Release builds, the check object shrinks from 182,080 to 164,800 bytes (-17,280, 9.49%). Standalone clang-tidy shrinks from 90,985,432 to 90,968,728 bytes (-16,704); stripped it shrinks from 75,636,880 to 75,620,368 bytes (-16,512). A clang-tidy-only llvm multicall link shrinks from 74,022,344 to 74,022,184 bytes (-160); stripped it shrinks from 60,500,544 to 60,500,528 bytes (-16). The removed vtable saves 8,328 bytes in __DATA_CONST,__const, but Mach-O page alignment absorbs most of that in the file size. Paired benchmarks found no performance regression. Child CPU changes and bootstrap 95% confidence intervals were: startup +0.18% [-0.35%, +0.73%], the affected check +0.08% [-0.47%, +0.62%], and modernize-use-nullptr +0.28% [-0.24%, +0.81%]. The existing misc/static-initialization-cycle.cpp test passes through check_clang_tidy.py for C++11, C++14, C++17, C++20, C++23, and C++26. Baseline and candidate check lists and diagnostics are identical. Work towards #202616 >From 43627caf4100d1ac803ba21f78849480961c8a02 Mon Sep 17 00:00:00 2001 From: David Zbarsky <[email protected]> Date: Tue, 9 Jun 2026 09:08:31 -0400 Subject: [PATCH] [clang-tidy] Reuse static initialization cycle visitor StaticInitializationCycleCheck used separate dynamic recursive AST visitors to build its graph and collect uses. Each subclass emitted a complete DynamicRecursiveASTVisitor vtable. Reuse VarUseGraphBuilder in an explicit collection mode. This preserves the existing traversal filters while removing VarUseCollector and its vtable. A static assertion keeps the merged visitor at five pointer widths. On arm64 macOS Release builds, the check object shrinks from 182,080 to 164,800 bytes (-17,280, 9.49%). Standalone clang-tidy shrinks from 90,985,432 to 90,968,728 bytes (-16,704); stripped it shrinks from 75,636,880 to 75,620,368 bytes (-16,512). A clang-tidy-only llvm multicall link shrinks from 74,022,344 to 74,022,184 bytes (-160); stripped it shrinks from 60,500,544 to 60,500,528 bytes (-16). The removed vtable saves 8,328 bytes in __DATA_CONST,__const, but Mach-O page alignment absorbs most of that in the file size. Paired benchmarks found no performance regression. Child CPU changes and bootstrap 95% confidence intervals were: startup +0.18% [-0.35%, +0.73%], the affected check +0.08% [-0.47%, +0.62%], and modernize-use-nullptr +0.28% [-0.24%, +0.81%]. The existing misc/static-initialization-cycle.cpp test passes through check_clang_tidy.py for C++11, C++14, C++17, C++20, C++23, and C++26. Baseline and candidate check lists and diagnostics are identical. --- .../misc/StaticInitializationCycleCheck.cpp | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp index 3d1fc88e7233a..c869b45fb9069 100644 --- a/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/StaticInitializationCycleCheck.cpp @@ -125,7 +125,6 @@ class VarUseNode { bool empty() const { return Uses.empty(); } unsigned size() const { return Uses.size(); } - friend class VarUseCollector; friend class VarUseGraphBuilder; friend class VarUseGraph; }; @@ -171,34 +170,54 @@ class VarUseGraph { friend class VarUseGraphBuilder; }; -// Collect static variable references and static function calls. -// This is used with initializer expressions and function body statements. -// At initializer expressions only statements (and expressions) should be -// traversed. But for functions declarations are needed too (to reach -// initializations of variables) (only inside the given function). -class VarUseCollector : public DynamicRecursiveASTVisitor { - VarUseNode *Node; +// Build the complete graph by visiting all static variables and functions and +// add all "usages" (children in the graph) to it. +// Every variable and function is visited once (at canonical declaration or the +// definition). When visiting an object, a node for it may already exist +// (without added children) if a reference to it was found already. +class VarUseGraphBuilder : public DynamicRecursiveASTVisitor { VarUseGraph &G; - const DeclContext *DC; + VarUseNode *Node = nullptr; + const DeclContext *DC = nullptr; + + // Collect static variable references and static function calls from an + // initializer expression or function body. + void collectUses(VarUseNode *N, Stmt *S) { + assert(!Node); + Node = N; + DC = N->isFunction() ? N->getFunction() : nullptr; + TraverseStmt(S); + Node = nullptr; + DC = nullptr; + } public: - VarUseCollector(VarUseNode *N, VarUseGraph &G) - : Node(N), G(G), DC(N->isFunction() ? N->getFunction() : nullptr) {} + VarUseGraphBuilder(VarUseGraph &G) : G(G) {} bool TraverseType(QualType T, bool TraverseQualifier) override { - return true; + if (Node) + return true; + return DynamicRecursiveASTVisitor::TraverseType(T, TraverseQualifier); } bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier) override { - return true; + if (Node) + return true; + return DynamicRecursiveASTVisitor::TraverseTypeLoc(TL, TraverseQualifier); + } + bool TraverseAttr(Attr *At) override { + if (Node) + return true; + return DynamicRecursiveASTVisitor::TraverseAttr(At); } - bool TraverseAttr(Attr *At) override { return true; } bool TraverseDecl(Decl *D) override { - if (D && DC && DC->containsDecl(D)) + if (!Node || (D && DC && DC->containsDecl(D))) return DynamicRecursiveASTVisitor::TraverseDecl(D); return true; } bool VisitDeclRefExpr(DeclRefExpr *DRE) override { + if (!Node) + return true; if (const auto *VarD = dyn_cast<VarDecl>(DRE->getDecl())) { if (!shouldIgnoreRef(DRE, Node->getDecl()) && (VarD->hasGlobalStorage() || VarD->isStaticLocal())) @@ -208,6 +227,8 @@ class VarUseCollector : public DynamicRecursiveASTVisitor { } bool VisitCallExpr(CallExpr *CE) override { + if (!Node) + return true; if (const FunctionDecl *F = CE->getDirectCallee()) { if (F->isGlobal() || F->isStatic()) { const FunctionDecl *Def = F->getDefinition(); @@ -217,42 +238,33 @@ class VarUseCollector : public DynamicRecursiveASTVisitor { } return true; } -}; - -// Build the complete graph by visiting all static variables and functions and -// add all "usages" (children in the graph) to it. -// Every variable and function is visited once (at canonical declaration or the -// definition). When visiting an object, a node for it may already exist -// (without added children) if a reference to it was found already. -class VarUseGraphBuilder : public DynamicRecursiveASTVisitor { - VarUseGraph &G; - -public: - VarUseGraphBuilder(VarUseGraph &G) : G(G) {} bool VisitVarDecl(VarDecl *VD) override { + if (Node) + return true; if ((VD->hasGlobalStorage() || VD->isStaticLocal()) && VD->isCanonicalDecl()) { if (VarDecl *InitD = VD->getInitializingDeclaration()) { VarUseNode *N = G.addNode(VD); - VarUseCollector Collector(N, G); - Collector.TraverseStmt(InitD->getInit()); + collectUses(N, InitD->getInit()); } } return true; } bool VisitFunctionDecl(FunctionDecl *FD) override { + if (Node) + return true; if (FD->isGlobal() || FD->isStatic()) { if (Stmt *Body = FD->getBody()) { VarUseNode *N = G.addNode(FD); - VarUseCollector Collector(N, G); - Collector.TraverseStmt(Body); + collectUses(N, Body); } } return true; } }; +static_assert(sizeof(VarUseGraphBuilder) == 5 * sizeof(void *)); } // namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
