Skip to content

[Analysis][EphemeralValuesAnalysis][NFCI] Remove EphemeralValuesCache class #132454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 22, 2025

Conversation

vporpo
Copy link
Contributor

@vporpo vporpo commented Mar 21, 2025

This is a follow-up to #130210. The EphemeralValuesAnalysis pass used to return an EphemeralValuesCache object which used to hold the ephemeral values and used to provide a lazy collection of the ephemeral values, and an invalidation using the clear() function.

This patch removes the EphemeralValuesCache class completely and instead returns the SmallVector containing the ephemeral values.

… class

This is a follow-up to llvm#130210.
The EphemeralValuesAnalysis pass used to return an EphemeralValuesCache
object which used to hold the ephemeral values and used to provide a lazy
collection of the ephemeral values, and an invalidation using the `clear()`
function.

This patch removes the EphemeralValuesCache class completely and instead
returns the SmallVector containing the ephemeral values.
@vporpo vporpo requested review from nikic and aeubanks March 21, 2025 19:07
@llvmbot llvmbot added llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms labels Mar 21, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-analysis

Author: vporpo (vporpo)

Changes

This is a follow-up to #130210. The EphemeralValuesAnalysis pass used to return an EphemeralValuesCache object which used to hold the ephemeral values and used to provide a lazy collection of the ephemeral values, and an invalidation using the clear() function.

This patch removes the EphemeralValuesCache class completely and instead returns the SmallVector containing the ephemeral values.


Full diff: https://github.com/llvm/llvm-project/pull/132454.diff

7 Files Affected:

  • (modified) llvm/include/llvm/Analysis/EphemeralValuesCache.h (+1-23)
  • (modified) llvm/include/llvm/Analysis/InlineCost.h (+13-12)
  • (modified) llvm/lib/Analysis/EphemeralValuesCache.cpp (+4-7)
  • (modified) llvm/lib/Analysis/InlineCost.cpp (+11-8)
  • (modified) llvm/lib/Transforms/IPO/Inliner.cpp (+2-1)
  • (modified) llvm/test/Transforms/Inline/cgscc-incremental-invalidate.ll (-3)
  • (modified) llvm/unittests/Analysis/EphemeralValuesCacheTest.cpp (+15-14)
diff --git a/llvm/include/llvm/Analysis/EphemeralValuesCache.h b/llvm/include/llvm/Analysis/EphemeralValuesCache.h
index 2b50de9d22259..5e9664e00d961 100644
--- a/llvm/include/llvm/Analysis/EphemeralValuesCache.h
+++ b/llvm/include/llvm/Analysis/EphemeralValuesCache.h
@@ -23,35 +23,13 @@ class Function;
 class AssumptionCache;
 class Value;
 
-/// A cache of ephemeral values within a function.
-class EphemeralValuesCache {
-  SmallPtrSet<const Value *, 32> EphValues;
-  Function &F;
-  AssumptionCache &AC;
-  bool Collected = false;
-
-  void collectEphemeralValues();
-
-public:
-  EphemeralValuesCache(Function &F, AssumptionCache &AC) : F(F), AC(AC) {}
-  void clear() {
-    EphValues.clear();
-    Collected = false;
-  }
-  const SmallPtrSetImpl<const Value *> &ephValues() {
-    if (!Collected)
-      collectEphemeralValues();
-    return EphValues;
-  }
-};
-
 class EphemeralValuesAnalysis
     : public AnalysisInfoMixin<EphemeralValuesAnalysis> {
   friend AnalysisInfoMixin<EphemeralValuesAnalysis>;
   static AnalysisKey Key;
 
 public:
-  using Result = EphemeralValuesCache;
+  using Result = SmallPtrSet<const Value *, 32>;
   Result run(Function &F, FunctionAnalysisManager &FAM);
 };
 
diff --git a/llvm/include/llvm/Analysis/InlineCost.h b/llvm/include/llvm/Analysis/InlineCost.h
index 90ee75773957a..8a51f6cc356cf 100644
--- a/llvm/include/llvm/Analysis/InlineCost.h
+++ b/llvm/include/llvm/Analysis/InlineCost.h
@@ -15,6 +15,7 @@
 
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/Analysis/EphemeralValuesCache.h"
 #include "llvm/Analysis/InlineModelFeatureMaps.h"
 #include "llvm/IR/PassManager.h"
 #include <cassert>
@@ -31,7 +32,6 @@ class Function;
 class ProfileSummaryInfo;
 class TargetTransformInfo;
 class TargetLibraryInfo;
-class EphemeralValuesCache;
 
 namespace InlineConstants {
 // Various thresholds used by inline cost analysis.
@@ -280,23 +280,24 @@ InlineCost getInlineCost(
     function_ref<const TargetLibraryInfo &(Function &)> GetTLI,
     function_ref<BlockFrequencyInfo &(Function &)> GetBFI = nullptr,
     ProfileSummaryInfo *PSI = nullptr, OptimizationRemarkEmitter *ORE = nullptr,
-    function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache =
-        nullptr);
+    function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+        GetEphValuesCache = nullptr);
 
 /// Get an InlineCost with the callee explicitly specified.
 /// This allows you to calculate the cost of inlining a function via a
 /// pointer. This behaves exactly as the version with no explicit callee
 /// parameter in all other respects.
 //
-InlineCost getInlineCost(
-    CallBase &Call, Function *Callee, const InlineParams &Params,
-    TargetTransformInfo &CalleeTTI,
-    function_ref<AssumptionCache &(Function &)> GetAssumptionCache,
-    function_ref<const TargetLibraryInfo &(Function &)> GetTLI,
-    function_ref<BlockFrequencyInfo &(Function &)> GetBFI = nullptr,
-    ProfileSummaryInfo *PSI = nullptr, OptimizationRemarkEmitter *ORE = nullptr,
-    function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache =
-        nullptr);
+InlineCost
+getInlineCost(CallBase &Call, Function *Callee, const InlineParams &Params,
+              TargetTransformInfo &CalleeTTI,
+              function_ref<AssumptionCache &(Function &)> GetAssumptionCache,
+              function_ref<const TargetLibraryInfo &(Function &)> GetTLI,
+              function_ref<BlockFrequencyInfo &(Function &)> GetBFI = nullptr,
+              ProfileSummaryInfo *PSI = nullptr,
+              OptimizationRemarkEmitter *ORE = nullptr,
+              function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+                  GetEphValuesCache = nullptr);
 
 /// Returns InlineResult::success() if the call site should be always inlined
 /// because of user directives, and the inlining is viable. Returns
diff --git a/llvm/lib/Analysis/EphemeralValuesCache.cpp b/llvm/lib/Analysis/EphemeralValuesCache.cpp
index 8b68df46950e1..f3cf655beef55 100644
--- a/llvm/lib/Analysis/EphemeralValuesCache.cpp
+++ b/llvm/lib/Analysis/EphemeralValuesCache.cpp
@@ -12,17 +12,14 @@
 
 namespace llvm {
 
-void EphemeralValuesCache::collectEphemeralValues() {
-  CodeMetrics::collectEphemeralValues(&F, &AC, EphValues);
-  Collected = true;
-}
-
 AnalysisKey EphemeralValuesAnalysis::Key;
 
-EphemeralValuesCache
+EphemeralValuesAnalysis::Result
 EphemeralValuesAnalysis::run(Function &F, FunctionAnalysisManager &FAM) {
   auto &AC = FAM.getResult<AssumptionAnalysis>(F);
-  return EphemeralValuesCache(F, AC);
+  SmallPtrSet<const Value *, 32> EphValues;
+  CodeMetrics::collectEphemeralValues(&F, &AC, EphValues);
+  return EphValues;
 }
 
 } // namespace llvm
diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index df212eb31950d..76c3dd8bb418a 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -271,7 +271,8 @@ class CallAnalyzer : public InstVisitor<CallAnalyzer, bool> {
   CallBase &CandidateCall;
 
   /// Getter for the cache of ephemeral values.
-  function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache = nullptr;
+  function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+      GetEphValuesCache = nullptr;
 
   /// Extension points for handling callsite features.
   // Called before a basic block was analyzed.
@@ -515,8 +516,8 @@ class CallAnalyzer : public InstVisitor<CallAnalyzer, bool> {
       function_ref<const TargetLibraryInfo &(Function &)> GetTLI = nullptr,
       ProfileSummaryInfo *PSI = nullptr,
       OptimizationRemarkEmitter *ORE = nullptr,
-      function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache =
-          nullptr)
+      function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+          GetEphValuesCache = nullptr)
       : TTI(TTI), GetAssumptionCache(GetAssumptionCache), GetBFI(GetBFI),
         GetTLI(GetTLI), PSI(PSI), F(Callee), DL(F.getDataLayout()), ORE(ORE),
         CandidateCall(Call), GetEphValuesCache(GetEphValuesCache) {}
@@ -1133,8 +1134,8 @@ class InlineCostCallAnalyzer final : public CallAnalyzer {
       ProfileSummaryInfo *PSI = nullptr,
       OptimizationRemarkEmitter *ORE = nullptr, bool BoostIndirect = true,
       bool IgnoreThreshold = false,
-      function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache =
-          nullptr)
+      function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+          GetEphValuesCache = nullptr)
       : CallAnalyzer(Callee, Call, TTI, GetAssumptionCache, GetBFI, GetTLI, PSI,
                      ORE, GetEphValuesCache),
         ComputeFullInlineCost(OptComputeFullInlineCost ||
@@ -2794,7 +2795,7 @@ InlineResult CallAnalyzer::analyze() {
   SmallPtrSet<const Value *, 32> EphValuesStorage;
   const SmallPtrSetImpl<const Value *> *EphValues = &EphValuesStorage;
   if (GetEphValuesCache)
-    EphValues = &GetEphValuesCache(F).ephValues();
+    EphValues = &GetEphValuesCache(F);
   else
     CodeMetrics::collectEphemeralValues(&F, &GetAssumptionCache(F),
                                         EphValuesStorage);
@@ -2980,7 +2981,8 @@ InlineCost llvm::getInlineCost(
     function_ref<const TargetLibraryInfo &(Function &)> GetTLI,
     function_ref<BlockFrequencyInfo &(Function &)> GetBFI,
     ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE,
-    function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache) {
+    function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+        GetEphValuesCache) {
   return getInlineCost(Call, Call.getCalledFunction(), Params, CalleeTTI,
                        GetAssumptionCache, GetTLI, GetBFI, PSI, ORE,
                        GetEphValuesCache);
@@ -3104,7 +3106,8 @@ InlineCost llvm::getInlineCost(
     function_ref<const TargetLibraryInfo &(Function &)> GetTLI,
     function_ref<BlockFrequencyInfo &(Function &)> GetBFI,
     ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE,
-    function_ref<EphemeralValuesCache &(Function &)> GetEphValuesCache) {
+    function_ref<EphemeralValuesAnalysis::Result &(Function &)>
+        GetEphValuesCache) {
 
   auto UserDecision =
       llvm::getAttributeBasedInliningDecision(Call, Callee, CalleeTTI, GetTLI);
diff --git a/llvm/lib/Transforms/IPO/Inliner.cpp b/llvm/lib/Transforms/IPO/Inliner.cpp
index 6b819a447bd77..d2eeb4468ea32 100644
--- a/llvm/lib/Transforms/IPO/Inliner.cpp
+++ b/llvm/lib/Transforms/IPO/Inliner.cpp
@@ -391,7 +391,8 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
       }
       // TODO: Shouldn't we be invalidating all analyses on F here?
       // The caller was modified, so invalidate Ephemeral Values.
-      FAM.getResult<EphemeralValuesAnalysis>(F).clear();
+      FAM.invalidate(
+          F, PreservedAnalyses::all().abandon(EphemeralValuesAnalysis::ID()));
 
       DidInline = true;
       InlinedCallees.insert(&Callee);
diff --git a/llvm/test/Transforms/Inline/cgscc-incremental-invalidate.ll b/llvm/test/Transforms/Inline/cgscc-incremental-invalidate.ll
index bc31bca614a40..7443581cf26b1 100644
--- a/llvm/test/Transforms/Inline/cgscc-incremental-invalidate.ll
+++ b/llvm/test/Transforms/Inline/cgscc-incremental-invalidate.ll
@@ -12,18 +12,15 @@
 ; CHECK: Invalidating analysis: LoopAnalysis on test1_f
 ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_f
 ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_f
-; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_f
 ; CHECK: Running analysis: DominatorTreeAnalysis on test1_g
 ; CHECK: Invalidating analysis: DominatorTreeAnalysis on test1_g
 ; CHECK: Invalidating analysis: LoopAnalysis on test1_g
 ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_g
 ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_g
-; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_g
 ; CHECK: Invalidating analysis: DominatorTreeAnalysis on test1_h
 ; CHECK: Invalidating analysis: LoopAnalysis on test1_h
 ; CHECK: Invalidating analysis: BranchProbabilityAnalysis on test1_h
 ; CHECK: Invalidating analysis: BlockFrequencyAnalysis on test1_h
-; CHECK: Invalidating analysis: EphemeralValuesAnalysis on test1_h
 ; CHECK-NOT: Invalidating analysis:
 ; CHECK: Running pass: DominatorTreeVerifierPass on test1_g
 ; CHECK-NEXT: Running analysis: DominatorTreeAnalysis on test1_g
diff --git a/llvm/unittests/Analysis/EphemeralValuesCacheTest.cpp b/llvm/unittests/Analysis/EphemeralValuesCacheTest.cpp
index c926cf97fcd69..e66c29a2cf4e8 100644
--- a/llvm/unittests/Analysis/EphemeralValuesCacheTest.cpp
+++ b/llvm/unittests/Analysis/EphemeralValuesCacheTest.cpp
@@ -8,9 +8,11 @@
 
 #include "llvm/Analysis/EphemeralValuesCache.h"
 #include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
 #include "llvm/AsmParser/Parser.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/PassInstrumentation.h"
 #include "llvm/Support/SourceMgr.h"
 #include "gmock/gmock-matchers.h"
 #include "gtest/gtest.h"
@@ -46,8 +48,6 @@ define void @foo(i8 %arg0, i8 %arg1) {
 )IR");
   Function *F = M->getFunction("foo");
   auto *BB = &*F->begin();
-  AssumptionCache AC(*F);
-  EphemeralValuesCache EVC(*F, AC);
   auto It = BB->begin();
   auto *C0 = &*It++;
   auto *Assume0 = &*It++;
@@ -55,20 +55,21 @@ define void @foo(i8 %arg0, i8 %arg1) {
   auto *C1 = &*It++;
   auto *Assume1 = &*It++;
   [[maybe_unused]] auto *Ret = &*It++;
-  // Check emphemeral values.
-  EXPECT_THAT(EVC.ephValues(),
-              testing::UnorderedElementsAre(C0, Assume0, C1, Assume1));
-  // Clear the cache and try again.
-  EVC.clear();
-  EXPECT_THAT(EVC.ephValues(),
-              testing::UnorderedElementsAre(C0, Assume0, C1, Assume1));
-  // Modify the IR, clear cache and recompute.
+  // Check ephemeral values.
+  FunctionAnalysisManager FAM;
+  FAM.registerPass([] { return EphemeralValuesAnalysis(); });
+  FAM.registerPass([] { return PassInstrumentationAnalysis(); });
+  FAM.registerPass([] { return AssumptionAnalysis(); });
+  FAM.registerPass([] { return TargetIRAnalysis(); });
+  auto Result = FAM.getResult<EphemeralValuesAnalysis>(*F);
+  EXPECT_THAT(Result, testing::UnorderedElementsAre(C0, Assume0, C1, Assume1));
+  // Modify the IR, invalidate and recompute.
   Assume1->eraseFromParent();
   C1->eraseFromParent();
-  EXPECT_THAT(EVC.ephValues(),
-              testing::UnorderedElementsAre(C0, Assume0, C1, Assume1));
-  EVC.clear();
-  EXPECT_THAT(EVC.ephValues(), testing::UnorderedElementsAre(C0, Assume0));
+  FAM.invalidate(
+      *F, PreservedAnalyses::all().abandon(EphemeralValuesAnalysis::ID()));
+  EXPECT_THAT(FAM.getResult<EphemeralValuesAnalysis>(*F),
+              testing::UnorderedElementsAre(C0, Assume0));
 }
 
 } // namespace

Copy link
Contributor

@aeubanks aeubanks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think the unittest is completely unnecessary, it's basically just checking that the analysis manager/invalidation works, which should be covered by other tests

@vporpo
Copy link
Contributor Author

vporpo commented Mar 21, 2025

I agree the unittest is not super useful anymore, but it's a sanity check that the EphemeralValuesAnalysis does return the expected values i.e., they get collected by run(). Checking the result after invalidation is pretty much redundant, I will drop it.

@aeubanks
Copy link
Contributor

it still seems unnecessary to me, but keeping it is fine

@vporpo vporpo merged commit 4adefcf into llvm:main Mar 22, 2025
11 checks passed
@nikic
Copy link
Contributor

nikic commented Mar 22, 2025

@vporpo
Copy link
Contributor Author

vporpo commented Mar 22, 2025

Good catch @nikic , I did notice a small compile-time increase compared to the previous one on the workload that was triggering the quadratic behavior but I assumed it was system noise, but now that I look into it I do see that the InlinerPass is 25% slower than before (but still way faster than without the Ephemeral Values Caching).

I will revert the patch to investigate it further.

vporpo added a commit that referenced this pull request Mar 22, 2025
@vporpo
Copy link
Contributor Author

vporpo commented Mar 24, 2025

After some investigation, it turns out the ~20% compile-time regression for the InlinerPass was actually due to run-to-run variance. In some runs the InlinerPass takes ~56s while in others it takes ~67s using the same clang binary.

The only major functional difference between this and the previous version that I can think of is that in this one we invalidate the EphemeralValuesAnalysis with FAM.invalidate() whenever a function gets inlined, whereas in the previous version we were calling EphemeralValuesCache::clear() without actually invalidating the analysis.
I collected profiles for SPASS and tramp3d-v4 (which showed compile-time regressions of 0.10% and 0.15% respectively) and nothing really pops out. But what both have in common is that they spend ~0.4% of compile-time in AnalysisManager<llvm::Function>::invalidate() according to perf, and in both cases the function runtime seems to have regressed by 0.05-0.10%. I am not super confident about these observations though, they might as well be noise.

In tramp3d-v4 invalidate() does show up towards the top of perf-diff and it seems to be regressing consistently, though InlinerPass::run() seems to regress by just 0.01%

     0.41%     +0.07%  clang-21                 [.] llvm::AnalysisManager<llvm::Function>::invalidate(llvm::Function&, llvm::PreservedAnalyses const&)
     ...
     0.09%     +0.01%  clang-21                 [.] llvm::InlinerPass::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&)

Same for SPASS:

     0.40%     +0.04%  clang-21                  [.] llvm::AnalysisManager<llvm::Function>::invalidate(llvm::Function&, llvm::PreservedAnalyses const&)
...
     0.05%     +0.00%  clang-21                  [.] llvm::InlinerPass::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager<llvm::LazyCallGraph::SCC, llvm::LazyCallGraph&>&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&)

The only way this makes sense is if the individual call to invalidate() is not causing a large regression, but adding a new Analysis is somehow slowing down invalidate(). Internally invalidate() seems to be iterating over lists of analyses, so perhaps this is the root cause, but I would need to look into it a bit more.

@aeubanks
Copy link
Contributor

I don't think invalidate() is iterating through lists of analyses, it should only be looking at the analysis being invalidated and any analyses that that analysis's invalidate() also says to invalidate.

@aeubanks
Copy link
Contributor

ah sorry, I was looking at the wrong invalidate() method

@aeubanks
Copy link
Contributor

if it is the extra call to invalidate(), rather than the extra analysis causing more iterations in invalidate(), then we could expose a new AnalysisManager::invalidate() that only invalidates one analysis, although the fact that we should be invalidating all analyses anyway (the TODO) sorta suggests we shouldn't do that anyway.

this patch doesn't change the number of analyses though right? we still have EphemeralValuesAnalysis with and without this patch

@vporpo
Copy link
Contributor Author

vporpo commented Mar 25, 2025

if it is the extra call to invalidate(), rather than the extra analysis causing more iterations in invalidate()

This is the most likely explanation, but I was expecting an equal increase in time spent inside InlinerPass::run(), which doesn't seem to be the case.

this patch doesn't change the number of analyses though right? we still have EphemeralValuesAnalysis with and without this patch

Yes it's added in both cases so the overhead should be similar in both cases, except if invalidating analyses more often somehow causes invalidate to run slower for some reason, though this seems highly unlikely.

It seems that the perf stats I have collected are way too noisy, so I am not sure we can rely on them too much.

@vporpo
Copy link
Contributor Author

vporpo commented Mar 25, 2025

I commented out the new call to invalidate() and I am now getting 0.00% change compared to the previous version:

     0.40%     -0.00%  clang-21                 [.] llvm::AnalysisManager<llvm::Function>::invalidate(llvm::Function&, llvm::PreservedAnalyses const&)

So the ~0.05% diff in SPASS seems to be real.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants