From c0d2a8d4e2bf557fbc8b8fb94cbde681c6d9f243 Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh Date: Mon, 6 Oct 2025 15:37:02 +0200 Subject: [PATCH] [clang][analyzer] Register path- and syntax-analysis times / entry point Register and dump per-entry-point time taken by the path-sensitive and syntax-only analyses stages in milliseconds (SyntaxRunningTime, PathRunningTime). Also introduce a difference between unset stats and 0 for statistics categories for which it makes sense. So now if the syntax analysis does not run for a certain entry point, it's SyntaxRunningTime will have an empty value and not 0. This patch enables the timers if DumpEntryPointStatsToCSV is set, because in most cases you dump these stats to get a detailed view on analyzer performance. -- CPP-7097 --- .../Core/PathSensitive/EntryPointStats.h | 4 +- .../Core/PathSensitive/FunctionSummary.h | 8 +++ .../StaticAnalyzer/Core/EntryPointStats.cpp | 62 ++++++++++++++----- .../Frontend/AnalysisConsumer.cpp | 48 +++++++++++++- .../analyzer-stats/entry-point-stats.cpp | 10 +-- 5 files changed, 110 insertions(+), 22 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h index 448e40269ca2d..d02f77194ad8c 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h @@ -47,7 +47,7 @@ class BoolEPStat : public EntryPointStat { public: explicit BoolEPStat(llvm::StringLiteral Name); - unsigned value() const { return Value && *Value; } + std::optional value() const { return Value; } void set(bool V) { assert(!Value.has_value()); Value = V; @@ -98,7 +98,7 @@ class UnsignedEPStat : public EntryPointStat { public: explicit UnsignedEPStat(llvm::StringLiteral Name); - unsigned value() const { return Value.value_or(0); } + std::optional value() const { return Value; } void reset() { Value.reset(); } void set(unsigned V) { assert(!Value.has_value()); diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h index 761395260a0cf..db4aec7c84754 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/FunctionSummary.h @@ -48,6 +48,9 @@ class FunctionSummariesTy { /// The number of times the function has been inlined. unsigned TimesInlined : 32; + /// Running time for syntax-based AST analysis in milliseconds. + std::optional SyntaxRunningTime = std::nullopt; + FunctionSummary() : TotalBasicBlocks(0), InlineChecked(0), MayInline(0), TimesInlined(0) {} @@ -69,6 +72,11 @@ class FunctionSummariesTy { return I; } + FunctionSummary const *findSummary(const Decl *D) const { + auto I = Map.find(D); + return I == Map.end() ? nullptr : &I->second; + } + void markMayInline(const Decl *D) { MapTy::iterator I = findOrInsertSummary(D); I->second.InlineChecked = 1; diff --git a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp index 62ae62f2f2154..33c5df8132beb 100644 --- a/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp +++ b/clang/lib/StaticAnalyzer/Core/EntryPointStats.cpp @@ -33,7 +33,15 @@ struct Registry { struct Snapshot { const Decl *EntryPoint; - std::vector BoolStatValues; + // Boolean statistics are always set explicitly. If they are not set, their + // value is absent resulting in empty CSV cells + std::vector> BoolStatValues; + // Explicitly set statistics may not have a value set, so they are separate + // from other unsigned statistics + std::vector> UnsignedExplicitlySetStatValues; + // These are counting and maximizing statistics that initialize to 0, which + // is meaningful even if they are never updated, so their value is always + // present. std::vector UnsignedStatValues; void dumpAsCSV(llvm::raw_ostream &OS) const; @@ -48,10 +56,14 @@ static llvm::ManagedStatic StatsRegistry; namespace { template void enumerateStatVectors(const Callback &Fn) { + // This order is important, it matches the order of Snapshot 3 fields: + // - BoolStatValues Fn(StatsRegistry->BoolStats); + // - UnsignedExplicitlySetStatValues + Fn(StatsRegistry->UnsignedStats); + // - UnsignedStatValues Fn(StatsRegistry->CounterStats); Fn(StatsRegistry->UnsignedMaxStats); - Fn(StatsRegistry->UnsignedStats); } } // namespace @@ -120,11 +132,21 @@ UnsignedEPStat::UnsignedEPStat(llvm::StringLiteral Name) StatsRegistry->UnsignedStats.push_back(this); } +static std::vector> +consumeUnsignedExplicitlySetStats() { + std::vector> Result; + Result.reserve(StatsRegistry->UnsignedStats.size()); + for (auto *M : StatsRegistry->UnsignedStats) { + Result.push_back(M->value()); + M->reset(); + } + return Result; +} + static std::vector consumeUnsignedStats() { std::vector Result; Result.reserve(StatsRegistry->CounterStats.size() + - StatsRegistry->UnsignedMaxStats.size() + - StatsRegistry->UnsignedStats.size()); + StatsRegistry->UnsignedMaxStats.size()); for (auto *M : StatsRegistry->CounterStats) { Result.push_back(M->value()); M->reset(); @@ -133,10 +155,6 @@ static std::vector consumeUnsignedStats() { Result.push_back(M->value()); M->reset(); } - for (auto *M : StatsRegistry->UnsignedStats) { - Result.push_back(M->value()); - M->reset(); - } return Result; } @@ -159,6 +177,17 @@ static std::string getUSR(const Decl *D) { } void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { + auto printAsBoolOpt = [&OS](std::optional B) { + OS << (B.has_value() ? (*B ? "true" : "false") : ""); + }; + auto printAsUnsignOpt = [&OS](std::optional U) { + OS << (U.has_value() ? std::to_string(*U) : ""); + }; + auto commaIfNeeded = [&OS](const auto &Vec1, const auto &Vec2) { + if (!Vec1.empty() && !Vec2.empty()) + OS << ","; + }; + OS << '"'; llvm::printEscapedString(getUSR(EntryPoint), OS); OS << "\",\""; @@ -166,14 +195,15 @@ void Registry::Snapshot::dumpAsCSV(llvm::raw_ostream &OS) const { llvm::printEscapedString( clang::AnalysisDeclContext::getFunctionName(EntryPoint), OS); OS << "\","; - auto PrintAsBool = [&OS](bool B) { OS << (B ? "true" : "false"); }; - llvm::interleave(BoolStatValues, OS, PrintAsBool, ","); - OS << ((BoolStatValues.empty() || UnsignedStatValues.empty()) ? "" : ","); + llvm::interleave(BoolStatValues, OS, printAsBoolOpt, ","); + commaIfNeeded(BoolStatValues, UnsignedExplicitlySetStatValues); + llvm::interleave(UnsignedExplicitlySetStatValues, OS, printAsUnsignOpt, ","); + commaIfNeeded(UnsignedExplicitlySetStatValues, UnsignedStatValues); llvm::interleave(UnsignedStatValues, OS, [&OS](unsigned U) { OS << U; }, ","); } -static std::vector consumeBoolStats() { - std::vector Result; +static std::vector> consumeBoolStats() { + std::vector> Result; Result.reserve(StatsRegistry->BoolStats.size()); for (auto *M : StatsRegistry->BoolStats) { Result.push_back(M->value()); @@ -184,9 +214,11 @@ static std::vector consumeBoolStats() { void EntryPointStat::takeSnapshot(const Decl *EntryPoint) { auto BoolValues = consumeBoolStats(); + auto UnsignedExplicitlySetValues = consumeUnsignedExplicitlySetStats(); auto UnsignedValues = consumeUnsignedStats(); - StatsRegistry->Snapshots.push_back( - {EntryPoint, std::move(BoolValues), std::move(UnsignedValues)}); + StatsRegistry->Snapshots.push_back({EntryPoint, std::move(BoolValues), + std::move(UnsignedExplicitlySetValues), + std::move(UnsignedValues)}); } void EntryPointStat::dumpStatsAsCSV(llvm::StringRef FileName) { diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index cf01e2f37c662..a5efa901b0714 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -39,6 +39,7 @@ #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -142,7 +143,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, DigestAnalyzerOptions(); if (Opts.AnalyzerDisplayProgress || Opts.PrintStats || - Opts.ShouldSerializeStats) { + Opts.ShouldSerializeStats || !Opts.DumpEntryPointStatsToCSV.empty()) { AnalyzerTimers = std::make_unique( "analyzer", "Analyzer timers"); SyntaxCheckTimer = std::make_unique( @@ -706,6 +707,7 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { } static UnsignedEPStat PathRunningTime("PathRunningTime"); +static UnsignedEPStat SyntaxRunningTime("SyntaxRunningTime"); void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, ExprEngine::InliningModes IMode, @@ -744,6 +746,8 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, SyntaxCheckTimer->stopTimer(); llvm::TimeRecord CheckerEndTime = SyntaxCheckTimer->getTotalTime(); CheckerEndTime -= CheckerStartTime; + FunctionSummaries.findOrInsertSummary(D)->second.SyntaxRunningTime = + std::lround(CheckerEndTime.getWallTime() * 1000); DisplayTime(CheckerEndTime); } } @@ -758,6 +762,36 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, } } +namespace { +template +static clang::Decl *preferDefinitionImpl(clang::Decl *D) { + if (auto *X = dyn_cast(D)) + if (auto *Def = X->getDefinition()) + return Def; + return D; +} + +template <> clang::Decl *preferDefinitionImpl(clang::Decl *D) { + if (const auto *X = dyn_cast(D)) { + for (auto *I : X->redecls()) + if (I->hasBody()) + return I; + } + return D; +} + +static Decl *getDefinitionOrCanonicalDecl(Decl *D) { + assert(D); + D = D->getCanonicalDecl(); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + D = preferDefinitionImpl(D); + assert(D); + return D; +} +} // namespace + //===----------------------------------------------------------------------===// // Path-sensitive checking. //===----------------------------------------------------------------------===// @@ -774,6 +808,16 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, if (!Mgr->getAnalysisDeclContext(D)->getAnalysis()) return; + const Decl *DefDecl = getDefinitionOrCanonicalDecl(D); + + // Get the SyntaxRunningTime from the function summary, because it is computed + // during the AM_Syntax analysis, which is done at a different point in time + // and in different order, but always before AM_Path. + if (const auto *Summary = FunctionSummaries.findSummary(DefDecl); + Summary && Summary->SyntaxRunningTime.has_value()) { + SyntaxRunningTime.set(*Summary->SyntaxRunningTime); + } + ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); // Execute the worklist algorithm. @@ -788,6 +832,8 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngineTimer->stopTimer(); llvm::TimeRecord ExprEngineEndTime = ExprEngineTimer->getTotalTime(); ExprEngineEndTime -= ExprEngineStartTime; + PathRunningTime.set(static_cast( + std::lround(ExprEngineEndTime.getWallTime() * 1000))); DisplayTime(ExprEngineEndTime); } diff --git a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp index 9cbe04550a8d3..b9099c6248108 100644 --- a/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp +++ b/clang/test/Analysis/analyzer-stats/entry-point-stats.cpp @@ -8,6 +8,8 @@ // CHECK-NEXT: "c:@F@fib#i#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "fib(unsigned int)", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", +// CHECK-NEXT: "SyntaxRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -39,12 +41,13 @@ // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxReachableSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxTimeSpentSolvingZ3Queries": "{{[0-9]+}}", -// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}", -// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}" +// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}" // CHECK-NEXT: }, // CHECK-NEXT: "c:@F@main#I#**C#": { // CHECK-NEXT: "File": "{{.*}}entry-point-stats.cpp", // CHECK-NEXT: "DebugName": "main(int, char **)", +// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}", +// CHECK-NEXT: "SyntaxRunningTime": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocks": "{{[0-9]+}}", // CHECK-NEXT: "NumBlocksUnreachable": "{{[0-9]+}}", // CHECK-NEXT: "NumCTUSteps": "{{[0-9]+}}", @@ -76,8 +79,7 @@ // CHECK-NEXT: "MaxQueueSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxReachableSize": "{{[0-9]+}}", // CHECK-NEXT: "MaxTimeSpentSolvingZ3Queries": "{{[0-9]+}}", -// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}", -// CHECK-NEXT: "PathRunningTime": "{{[0-9]+}}" +// CHECK-NEXT: "MaxValidBugClassSize": "{{[0-9]+}}" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NOT: non_entry_point