Author: DEBADRIBASAK
Date: 2026-02-17T12:29:07+01:00
New Revision: 74044a797eaed56fad32ffff22cd3304d836ef7a

URL: 
https://github.com/llvm/llvm-project/commit/74044a797eaed56fad32ffff22cd3304d836ef7a
DIFF: 
https://github.com/llvm/llvm-project/commit/74044a797eaed56fad32ffff22cd3304d836ef7a.diff

LOG: [LifetimeSafety] Add bailout for large CFGs (#170444)

This PR introduces a flag for setting a threshold size for CFG blocks
above which lifetime safety analysis will skip processing those CFGs.
The major contributor of compilation time increase due to lifetime
safety analysis is the costly join operation during loan propagation.
This can be avoided at the cost of introducing some false negatives by
ignoring some large CFG blocks.

The `block-size-threshold` flag accepts an integer value which serves as
the threshold. CFG blocks with size above this threshold are ignored.
This value is only used if an integer > 0 is passed to it. By default it
is set to 0 and no CFG blocks are skipped during analysis. The CFG block
size refers to the number of facts associated with a CFG block. This PR
also adds a debug-only option that dumps the sizes of CFG blocks
associated with an analysis context:

Example output (for `llvm-project/llvm/lib/Demangle/Demangle.cpp`):

With different values for `MaxCfgBlocks` the values for
`task-clock:uppp_event` are as follows (the files are taken from there
top files
[here](https://llvm-compile-time-tracker.com/compare_clang.php?from=128eacfaba78162c944c073270db02e237b7b851&to=e39caf5a04aadf6053470b7843a4d987250083f1&stat=instructions%3Au&sortBy=absolute-difference))


| | CompilerInvocation.cpp | | | SemaARM.cpp | | | X86ISelLowering.cpp |
| |

|:---------------:|:----------------------:|:----------------------:|:----------------------:|:--------------:|:------------------------:|:----------------------:|:-------------------:|:-----------------------:|:-----------------------:|
| | No bailout | >5000 | >1000 | No bailout | >5000 | >1000 | No bailout
| >5000 | >1000 |
| Total Time | 27,143,500,000 | 27,108,750,000 (-0.1%) | 27,058,000,000
(-0.3%) | 13,819,750,000 | 13,807,750,000 (-0.086%) | 13,790,500,000
(-0.2%) | 49,580,250,000 | 49,248,500,000 (-0.67%) | 49,422,000,000
(-0.42%) |
| Lifetime Safety | 97,000,000 | 95,500,000 (-1.5%) | 91,250,000 (-5.9%)
| 68,750,000 | 64,500,000 (-6.18%) | 63,750,000 (-7.27%) | 131,500,000 |
130,250,000 (-0.9%) | 124,500,000 (-5.3%) |

Added: 
    clang/test/Sema/warn-lifetime-safety-cfg-bailout.cpp

Modified: 
    clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
    clang/include/clang/Basic/LangOptions.def
    clang/include/clang/Options/Options.td
    clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
    clang/unittests/Analysis/LifetimeSafetyTest.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 6148f86091110..7761a5c24c606 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -28,6 +28,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/MovedLoans.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
 #include "clang/Analysis/AnalysisDeclContext.h"
+#include <cstdint>
 #include <memory>
 
 namespace clang::lifetimes {
@@ -39,6 +40,12 @@ enum class Confidence : uint8_t {
   Definite // Reported as a definite error (-Wlifetime-safety-permissive)
 };
 
+struct LifetimeSafetyOpts {
+  /// Maximum number of CFG blocks to analyze. Functions with larger CFGs will
+  /// be skipped.
+  size_t MaxCFGBlocks;
+};
+
 /// Enum to track functions visible across or within TU.
 enum class SuggestionScope {
   CrossTU, // For suggestions on declarations visible across Translation Units.
@@ -130,7 +137,8 @@ struct LifetimeFactory {
 class LifetimeSafetyAnalysis {
 public:
   LifetimeSafetyAnalysis(AnalysisDeclContext &AC,
-                         LifetimeSafetySemaHelper *SemaHelper);
+                         LifetimeSafetySemaHelper *SemaHelper,
+                         const LifetimeSafetyOpts &LSOpts);
 
   void run();
 
@@ -144,6 +152,7 @@ class LifetimeSafetyAnalysis {
 private:
   AnalysisDeclContext &AC;
   LifetimeSafetySemaHelper *SemaHelper;
+  const LifetimeSafetyOpts LSOpts;
   LifetimeFactory Factory;
   std::unique_ptr<FactManager> FactMgr;
   std::unique_ptr<LiveOriginsAnalysis> LiveOrigins;

diff  --git a/clang/include/clang/Basic/LangOptions.def 
b/clang/include/clang/Basic/LangOptions.def
index 45e2777def4fa..08f102839b89e 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -504,6 +504,8 @@ LANGOPT(BoundsSafety, 1, 0, NotCompatible, "Bounds safety 
extension for C")
 
 LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Lifetime safety analysis 
for C++")
 
+LANGOPT(LifetimeSafetyMaxCFGBlocks, 32, 0, NotCompatible, "Skip LifetimeSafety 
analysis for functions with CFG block count exceeding this threshold. Specify 0 
for no limit")
+
 LANGOPT(EnableLifetimeSafetyInference, 1, 0, NotCompatible, "Lifetime safety 
inference analysis for C++")
 
 // TODO: Remove flag and default to end-of-TU analysis for lifetime safety 
after performance validation.

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index a274017953b1d..24b31fb3fefcc 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1967,6 +1967,14 @@ defm lifetime_safety : BoolFOption<
   NegFlag<SetFalse, [], [CC1Option], "Disable">,
   BothFlags<[], [CC1Option], " lifetime safety for C++">>;
 
+def lifetime_safety_max_cfg_blocks
+    : Joined<["-"], "lifetime-safety-max-cfg-blocks=">,
+      Group<m_Group>,
+      Visibility<[ClangOption, CC1Option]>,
+      HelpText<"Skip LifetimeSafety analysis for functions with CFG block "
+               "count exceeding this threshold. Specify 0 for no limit.">,
+      MarshallingInfoInt<LangOpts<"LifetimeSafetyMaxCFGBlocks">>;
+
 defm lifetime_safety_inference
     : BoolFOption<"lifetime-safety-inference",
                   LangOpts<"EnableLifetimeSafetyInference">, DefaultFalse,

diff  --git a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
index a6bea74c50b49..714f979fa5ee7 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeSafety.cpp
@@ -49,13 +49,25 @@ static void DebugOnlyFunction(AnalysisDeclContext &AC, 
const CFG &Cfg,
 #endif
 
 LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(
-    AnalysisDeclContext &AC, LifetimeSafetySemaHelper *SemaHelper)
-    : AC(AC), SemaHelper(SemaHelper) {}
+    AnalysisDeclContext &AC, LifetimeSafetySemaHelper *SemaHelper,
+    const LifetimeSafetyOpts &LSOpts)
+    : AC(AC), SemaHelper(SemaHelper), LSOpts(LSOpts) {}
 
 void LifetimeSafetyAnalysis::run() {
   llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
 
   const CFG &Cfg = *AC.getCFG();
+  if (LSOpts.MaxCFGBlocks > 0 && Cfg.getNumBlockIDs() > LSOpts.MaxCFGBlocks) {
+    DEBUG_WITH_TYPE(
+        "LifetimeSafety", std::string FuncName = "<unknown>";
+        if (const Decl *D = AC.getDecl()) if (const auto *ND =
+                                                  dyn_cast<NamedDecl>(D))
+            FuncName = ND->getQualifiedNameAsString();
+        llvm::dbgs() << "LifetimeSafety: Skipping function " << FuncName
+                     << "due to large CFG: " << Cfg.getNumBlockIDs()
+                     << " blocks (threshold: " << LSOpts.MaxCFGBlocks << 
")\n");
+    return;
+  }
 
   FactMgr = std::make_unique<FactManager>(AC, Cfg);
 
@@ -111,7 +123,11 @@ void collectLifetimeStats(AnalysisDeclContext &AC, 
OriginManager &OM,
 void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC,
                                LifetimeSafetySemaHelper *SemaHelper,
                                LifetimeSafetyStats &Stats, bool CollectStats) {
-  internal::LifetimeSafetyAnalysis Analysis(AC, SemaHelper);
+  LifetimeSafetyOpts LSOpts;
+  LSOpts.MaxCFGBlocks =
+      AC.getASTContext().getLangOpts().LifetimeSafetyMaxCFGBlocks;
+
+  internal::LifetimeSafetyAnalysis Analysis(AC, SemaHelper, LSOpts);
   Analysis.run();
   if (CollectStats)
     collectLifetimeStats(AC, Analysis.getFactManager().getOriginMgr(), Stats);

diff  --git a/clang/test/Sema/warn-lifetime-safety-cfg-bailout.cpp 
b/clang/test/Sema/warn-lifetime-safety-cfg-bailout.cpp
new file mode 100644
index 0000000000000..7c5d61e23e710
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-cfg-bailout.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety 
-lifetime-safety-max-cfg-blocks=3 -Wno-dangling -verify=bailout %s
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling 
-verify=bailout -verify=nobailout %s
+
+struct MyObj {
+  int id;
+  ~MyObj() {}  // Non-trivial destructor
+  MyObj operator+(MyObj);
+  void use() const;
+};
+
+struct [[gsl::Pointer()]] View {
+  View(const MyObj&); // Borrows from MyObj
+  View();
+  void use() const;
+};
+
+class TriviallyDestructedClass {
+  View a, b;
+};
+
+//===----------------------------------------------------------------------===//
+// Basic Definite Use-After-Free (-W...permissive)
+// These are cases where the pointer is guaranteed to be dangling at the use 
site.
+//===----------------------------------------------------------------------===//
+
+void single_block_cfg() {
+  MyObj* p;
+  {
+    MyObj s;
+    p = &s;     // bailout-warning {{object whose reference is captured does 
not live long enough}}
+  }             // bailout-note {{destroyed here}}
+  (void)*p;     // bailout-note {{later used here}}
+}
+
+void multiple_block_cfg() {
+  MyObj* p;
+  int a = 10;
+  MyObj safe;
+  {
+    if (a > 5) {
+      MyObj s;
+      p = &s;    // nobailout-warning {{object whose reference is captured 
does not live long enough}}
+    } else {     // nobailout-note {{destroyed here}}
+      p = &safe;
+    }     
+  }             
+  p->use();      // nobailout-note {{later used here}}
+}

diff  --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp 
b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 45611f856b3b2..a27f746fffb60 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -63,7 +63,10 @@ class LifetimeTestRunner {
     BuildOptions.AddLifetime = true;
 
     // Run the main analysis.
-    Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr);
+    LifetimeSafetyOpts LSOpts;
+    LSOpts.MaxCFGBlocks = 0;
+    Analysis =
+        std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr, 
LSOpts);
     Analysis->run();
 
     AnnotationToPointMap = Analysis->getFactManager().getTestPoints();


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to