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

Reply via email to