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
