[llvm] d9872ea - [CaptureTracking] Compute results with and without return capture (#195857)

via llvm-commits llvm-commits at lists.llvm.org
Thu May 7 08:08:39 PDT 2026


Author: Nikita Popov
Date: 2026-05-07T15:08:33Z
New Revision: d9872ea95f198982b41f90a3d5e8109cf4c72d46

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

LOG: [CaptureTracking] Compute results with and without return capture (#195857)

Currently PointerMayBeCaptured/FindEarliestEscape accept a
ReturnCaptures argument to determine whether returning the pointer
should be considered a capture. If you want to do a capture check for
both cases, you have to invoke the capture tracking API twice.

This PR instead changes the low level APIs to return a pair of capture
components, one where returns are considered non-capturing, and one
where they are considered capturing.

This is for use by https://github.com/llvm/llvm-project/pull/193939,
where AA wants both our usual captures-before (ignoring returns)
reasoning, and captures-anywhere (including returns). This will allow us
to do this in one (cached) query.

Added: 
    

Modified: 
    llvm/include/llvm/Analysis/AliasAnalysis.h
    llvm/include/llvm/Analysis/CaptureTracking.h
    llvm/lib/Analysis/BasicAliasAnalysis.cpp
    llvm/lib/Analysis/CaptureTracking.cpp
    llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
    llvm/lib/Transforms/Utils/SimplifyCFG.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h
index 845ccdbfce776..23fa20f17d92f 100644
--- a/llvm/include/llvm/Analysis/AliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/AliasAnalysis.h
@@ -39,6 +39,7 @@
 
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/CaptureTracking.h"
 #include "llvm/Analysis/MemoryLocation.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/PassManager.h"
@@ -155,19 +156,20 @@ struct LLVM_ABI CaptureAnalysis {
   /// are also considered.
   ///
   /// If I is nullptr, then captures at any point will be considered.
-  virtual CaptureComponents
-  getCapturesBefore(const Value *Object, const Instruction *I, bool OrAt) = 0;
+  virtual CaptureComponents getCapturesBefore(const Value *Object,
+                                              const Instruction *I, bool OrAt,
+                                              bool ReturnCaptures) = 0;
 };
 
 /// Context-free CaptureAnalysis provider, which computes and caches whether an
 /// object is captured in the function at all, but does not distinguish whether
 /// it was captured before or after the context instruction.
 class LLVM_ABI SimpleCaptureAnalysis final : public CaptureAnalysis {
-  SmallDenseMap<const Value *, CaptureComponents, 8> IsCapturedCache;
+  SmallDenseMap<const Value *, CaptureResult, 8> IsCapturedCache;
 
 public:
   CaptureComponents getCapturesBefore(const Value *Object, const Instruction *I,
-                                      bool OrAt) override;
+                                      bool OrAt, bool ReturnCaptures) override;
 };
 
 /// Context-sensitive CaptureAnalysis provider, which computes and caches the
@@ -183,7 +185,7 @@ class LLVM_ABI EarliestEscapeAnalysis final : public CaptureAnalysis {
   /// that may be captured (by any instruction, not necessarily the earliest
   /// one). The "earliest" instruction may be a conservative approximation,
   /// e.g. the first instruction in the function is always a legal choice.
-  DenseMap<const Value *, std::pair<Instruction *, CaptureComponents>>
+  DenseMap<const Value *, std::pair<Instruction *, CaptureResult>>
       EarliestEscapes;
 
   /// Reverse map from instruction to the objects it is the earliest escape for.
@@ -196,7 +198,7 @@ class LLVM_ABI EarliestEscapeAnalysis final : public CaptureAnalysis {
       : DT(DT), LI(LI), CI(CI) {}
 
   CaptureComponents getCapturesBefore(const Value *Object, const Instruction *I,
-                                      bool OrAt) override;
+                                      bool OrAt, bool ReturnCaptures) override;
 
   void removeInstruction(Instruction *I);
 };

diff  --git a/llvm/include/llvm/Analysis/CaptureTracking.h b/llvm/include/llvm/Analysis/CaptureTracking.h
index e652bc5a0a5a6..ca9d1c53de3ef 100644
--- a/llvm/include/llvm/Analysis/CaptureTracking.h
+++ b/llvm/include/llvm/Analysis/CaptureTracking.h
@@ -48,6 +48,14 @@ namespace llvm {
   LLVM_ABI bool PointerMayBeCaptured(const Value *V, bool ReturnCaptures,
                                      unsigned MaxUsesToExplore = 0);
 
+  /// Result of a PointerMayBeCaptured query, which includes the captured
+  /// components for both the case where return is considered a capture, and
+  /// where it isn't.
+  struct CaptureResult {
+    CaptureComponents WithoutRet;
+    CaptureComponents WithRet;
+  };
+
   /// Return which components of the pointer may be captured. Only consider
   /// components that are part of \p Mask. Once \p StopFn on the accumulated
   /// components returns true, the traversal is aborted early. By default, this
@@ -55,8 +63,8 @@ namespace llvm {
   /// This function only considers captures of the passed value via its def-use
   /// chain, without considering captures of values it may be based on, or
   /// implicit captures such as for external globals.
-  LLVM_ABI CaptureComponents PointerMayBeCaptured(
-      const Value *V, bool ReturnCaptures, CaptureComponents Mask,
+  LLVM_ABI CaptureResult PointerMayBeCaptured(
+      const Value *V, CaptureComponents Mask,
       function_ref<bool(CaptureComponents)> StopFn = capturesAnything,
       unsigned MaxUsesToExplore = 0);
 
@@ -106,10 +114,9 @@ namespace llvm {
   // not in a cycle.
   //
   // Only consider components that are part of \p Mask.
-  LLVM_ABI std::pair<Instruction *, CaptureComponents>
-  FindEarliestCapture(const Value *V, Function &F, bool ReturnCaptures,
-                      const DominatorTree &DT, CaptureComponents Mask,
-                      unsigned MaxUsesToExplore = 0);
+  LLVM_ABI std::pair<Instruction *, CaptureResult>
+  FindEarliestCapture(const Value *V, Function &F, const DominatorTree &DT,
+                      CaptureComponents Mask, unsigned MaxUsesToExplore = 0);
 
   /// Capture information for a specific Use.
   struct UseCaptureInfo {

diff  --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 8172cf29d890b..2d54d9815eb61 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -197,22 +197,18 @@ static bool areBothVScale(const Value *V1, const Value *V2) {
 
 CaptureAnalysis::~CaptureAnalysis() = default;
 
-CaptureComponents SimpleCaptureAnalysis::getCapturesBefore(const Value *Object,
-                                                           const Instruction *I,
-                                                           bool OrAt) {
+CaptureComponents SimpleCaptureAnalysis::getCapturesBefore(
+    const Value *Object, const Instruction *I, bool OrAt, bool ReturnCaptures) {
   if (!isIdentifiedFunctionLocal(Object))
     return CaptureComponents::Provenance;
 
-  auto [CacheIt, Inserted] =
-      IsCapturedCache.insert({Object, CaptureComponents::Provenance});
-  if (!Inserted)
-    return CacheIt->second;
+  auto [CacheIt, Inserted] = IsCapturedCache.try_emplace(Object);
+  if (Inserted)
+    CacheIt->second = PointerMayBeCaptured(
+        Object, CaptureComponents::Provenance,
+        [](CaptureComponents CC) { return capturesFullProvenance(CC); });
 
-  CaptureComponents Ret = PointerMayBeCaptured(
-      Object, /*ReturnCaptures=*/false, CaptureComponents::Provenance,
-      [](CaptureComponents CC) { return capturesFullProvenance(CC); });
-  CacheIt->second = Ret;
-  return Ret;
+  return ReturnCaptures ? CacheIt->second.WithRet : CacheIt->second.WithoutRet;
 }
 
 static bool isNotInCycle(const Instruction *I, const DominatorTree *DT,
@@ -226,21 +222,23 @@ static bool isNotInCycle(const Instruction *I, const DominatorTree *DT,
          !isPotentiallyReachableFromMany(Succs, BB, nullptr, DT, LI);
 }
 
-CaptureComponents
-EarliestEscapeAnalysis::getCapturesBefore(const Value *Object,
-                                          const Instruction *I, bool OrAt) {
+CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
+    const Value *Object, const Instruction *I, bool OrAt, bool ReturnCaptures) {
   if (!isIdentifiedFunctionLocal(Object))
     return CaptureComponents::Provenance;
 
   auto Iter = EarliestEscapes.try_emplace(Object);
   if (Iter.second) {
-    std::pair<Instruction *, CaptureComponents> EarliestCapture =
-        FindEarliestCapture(Object, *DT.getRoot()->getParent(),
-                            /*ReturnCaptures=*/false, DT,
-                            CaptureComponents::Provenance);
-    if (EarliestCapture.first)
-      Inst2Obj[EarliestCapture.first].push_back(Object);
-    Iter.first->second = EarliestCapture;
+    auto [EarliestInst, Res] = FindEarliestCapture(
+        Object, *DT.getRoot()->getParent(), DT, CaptureComponents::Provenance);
+    if (EarliestInst)
+      Inst2Obj[EarliestInst].push_back(Object);
+    Iter.first->second = {EarliestInst, Res};
+  }
+
+  if (ReturnCaptures) {
+    assert(!I && "Context instruction not supported if ReturnCaptures");
+    return Iter.first->second.second.WithRet;
   }
 
   auto IsNotCapturedBefore = [&]() {
@@ -263,7 +261,7 @@ EarliestEscapeAnalysis::getCapturesBefore(const Value *Object,
   };
   if (IsNotCapturedBefore())
     return CaptureComponents::None;
-  return Iter.first->second.second;
+  return Iter.first->second.second.WithoutRet;
 }
 
 void EarliestEscapeAnalysis::removeInstruction(Instruction *I) {
@@ -971,8 +969,8 @@ ModRefInfo BasicAAResult::getModRefInfo(const CallBase *Call,
   // non-volatile stores for them.
   if (isModOrRefSet(OtherMR) && !isa<Constant>(Object) && Call != Object &&
       (isa<AllocaInst>(Object) || !Call->hasFnAttr(Attribute::ReturnsTwice))) {
-    CaptureComponents CC =
-        AAQI.CA->getCapturesBefore(Object, Call, /*OrAt=*/false);
+    CaptureComponents CC = AAQI.CA->getCapturesBefore(
+        Object, Call, /*OrAt=*/false, /*ReturnCaptures=*/false);
     if (capturesNothing(CC))
       OtherMR = ModRefInfo::NoModRef;
     else if (capturesReadProvenanceOnly(CC))
@@ -1666,13 +1664,13 @@ AliasResult BasicAAResult::aliasCheck(const Value *V1, LocationSize V1Size,
     // temporary store the nocapture argument's value in a temporary memory
     // location if that memory location doesn't escape. Or it may pass a
     // nocapture value to other functions as long as they don't capture it.
-    if (isEscapeSource(O1) &&
-        capturesNothing(AAQI.CA->getCapturesBefore(
-            O2, dyn_cast<Instruction>(O1), /*OrAt*/ true)))
+    if (isEscapeSource(O1) && capturesNothing(AAQI.CA->getCapturesBefore(
+                                  O2, dyn_cast<Instruction>(O1), /*OrAt=*/true,
+                                  /*ReturnCaptures=*/false)))
       return AliasResult::NoAlias;
-    if (isEscapeSource(O2) &&
-        capturesNothing(AAQI.CA->getCapturesBefore(
-            O1, dyn_cast<Instruction>(O2), /*OrAt*/ true)))
+    if (isEscapeSource(O2) && capturesNothing(AAQI.CA->getCapturesBefore(
+                                  O1, dyn_cast<Instruction>(O2), /*OrAt=*/true,
+                                  /*ReturnCaptures=*/false)))
       return AliasResult::NoAlias;
   }
 

diff  --git a/llvm/lib/Analysis/CaptureTracking.cpp b/llvm/lib/Analysis/CaptureTracking.cpp
index 22229d9c26b3b..c34fde8d11704 100644
--- a/llvm/lib/Analysis/CaptureTracking.cpp
+++ b/llvm/lib/Analysis/CaptureTracking.cpp
@@ -57,32 +57,32 @@ bool CaptureTracker::shouldExplore(const Use *U) { return true; }
 
 namespace {
 struct SimpleCaptureTracker : public CaptureTracker {
-  explicit SimpleCaptureTracker(bool ReturnCaptures, CaptureComponents Mask,
+  explicit SimpleCaptureTracker(CaptureComponents Mask,
                                 function_ref<bool(CaptureComponents)> StopFn)
-      : ReturnCaptures(ReturnCaptures), Mask(Mask), StopFn(StopFn) {}
+      : Mask(Mask), StopFn(StopFn) {}
 
   void tooManyUses() override {
     LLVM_DEBUG(dbgs() << "Captured due to too many uses\n");
     CC = Mask;
+    CCWithRet = Mask;
   }
 
   Action captured(const Use *U, UseCaptureInfo CI) override {
-    if (isa<ReturnInst>(U->getUser()) && !ReturnCaptures)
-      return ContinueIgnoringReturn;
-
     if (capturesNothing(CI.UseCC & Mask))
       return Continue;
 
     LLVM_DEBUG(dbgs() << "Captured by: " << *U->getUser() << "\n");
-    CC |= CI.UseCC & Mask;
+    CCWithRet |= CI.UseCC & Mask;
+    if (!isa<ReturnInst>(U->getUser()))
+      CC |= CI.UseCC & Mask;
     return StopFn(CC) ? Stop : Continue;
   }
 
-  bool ReturnCaptures;
   CaptureComponents Mask;
   function_ref<bool(CaptureComponents)> StopFn;
 
   CaptureComponents CC = CaptureComponents::None;
+  CaptureComponents CCWithRet = CaptureComponents::None;
 };
 
 /// Only find pointer captures which happen before the given instruction. Uses
@@ -155,26 +155,26 @@ struct CapturesBefore : public CaptureTracker {
 // escape are not in a cycle.
 struct EarliestCaptures : public CaptureTracker {
 
-  EarliestCaptures(bool ReturnCaptures, Function &F, const DominatorTree &DT,
-                   CaptureComponents Mask)
-      : DT(DT), ReturnCaptures(ReturnCaptures), F(F), Mask(Mask) {}
+  EarliestCaptures(Function &F, const DominatorTree &DT, CaptureComponents Mask)
+      : DT(DT), F(F), Mask(Mask) {}
 
   void tooManyUses() override {
     CC = Mask;
+    CCWithRet = Mask;
     EarliestCapture = &*F.getEntryBlock().begin();
   }
 
   Action captured(const Use *U, UseCaptureInfo CI) override {
     Instruction *I = cast<Instruction>(U->getUser());
-    if (isa<ReturnInst>(I) && !ReturnCaptures)
-      return ContinueIgnoringReturn;
-
     if (capturesAnything(CI.UseCC & Mask)) {
-      if (!EarliestCapture)
-        EarliestCapture = I;
-      else
-        EarliestCapture = DT.findNearestCommonDominator(EarliestCapture, I);
-      CC |= CI.UseCC & Mask;
+      CCWithRet |= CI.UseCC & Mask;
+      if (!isa<ReturnInst>(I)) {
+        if (!EarliestCapture)
+          EarliestCapture = I;
+        else
+          EarliestCapture = DT.findNearestCommonDominator(EarliestCapture, I);
+        CC |= CI.UseCC & Mask;
+      }
     }
 
     // Continue analysis, as we need to see all potential captures.
@@ -182,24 +182,25 @@ struct EarliestCaptures : public CaptureTracker {
   }
 
   const DominatorTree &DT;
-  bool ReturnCaptures;
   Function &F;
   CaptureComponents Mask;
 
   Instruction *EarliestCapture = nullptr;
   CaptureComponents CC = CaptureComponents::None;
+  CaptureComponents CCWithRet = CaptureComponents::None;
 };
 } // namespace
 
-CaptureComponents llvm::PointerMayBeCaptured(
-    const Value *V, bool ReturnCaptures, CaptureComponents Mask,
-    function_ref<bool(CaptureComponents)> StopFn, unsigned MaxUsesToExplore) {
+CaptureResult
+llvm::PointerMayBeCaptured(const Value *V, CaptureComponents Mask,
+                           function_ref<bool(CaptureComponents)> StopFn,
+                           unsigned MaxUsesToExplore) {
   assert(!isa<GlobalValue>(V) &&
          "It doesn't make sense to ask whether a global is captured.");
 
   LLVM_DEBUG(dbgs() << "Captured?: " << *V << " = ");
 
-  SimpleCaptureTracker SCT(ReturnCaptures, Mask, StopFn);
+  SimpleCaptureTracker SCT(Mask, StopFn);
   PointerMayBeCaptured(V, &SCT, MaxUsesToExplore);
   if (capturesAnything(SCT.CC))
     ++NumCaptured;
@@ -207,14 +208,14 @@ CaptureComponents llvm::PointerMayBeCaptured(
     ++NumNotCaptured;
     LLVM_DEBUG(dbgs() << "not captured\n");
   }
-  return SCT.CC;
+  return {SCT.CC, SCT.CCWithRet};
 }
 
 bool llvm::PointerMayBeCaptured(const Value *V, bool ReturnCaptures,
                                 unsigned MaxUsesToExplore) {
-  return capturesAnything(
-      PointerMayBeCaptured(V, ReturnCaptures, CaptureComponents::All,
-                           capturesAnything, MaxUsesToExplore));
+  CaptureResult Res = PointerMayBeCaptured(V, CaptureComponents::All,
+                                           capturesAnything, MaxUsesToExplore);
+  return capturesAnything(ReturnCaptures ? Res.WithRet : Res.WithoutRet);
 }
 
 CaptureComponents llvm::PointerMayBeCapturedBefore(
@@ -225,9 +226,10 @@ CaptureComponents llvm::PointerMayBeCapturedBefore(
   assert(!isa<GlobalValue>(V) &&
          "It doesn't make sense to ask whether a global is captured.");
 
-  if (!DT)
-    return PointerMayBeCaptured(V, ReturnCaptures, Mask, StopFn,
-                                MaxUsesToExplore);
+  if (!DT) {
+    CaptureResult Res = PointerMayBeCaptured(V, Mask, StopFn, MaxUsesToExplore);
+    return ReturnCaptures ? Res.WithRet : Res.WithoutRet;
+  }
 
   CapturesBefore CB(ReturnCaptures, I, DT, IncludeI, LI, Mask, StopFn);
   PointerMayBeCaptured(V, &CB, MaxUsesToExplore);
@@ -248,20 +250,19 @@ bool llvm::PointerMayBeCapturedBefore(const Value *V, bool ReturnCaptures,
       capturesAnything, LI, MaxUsesToExplore));
 }
 
-std::pair<Instruction *, CaptureComponents>
-llvm::FindEarliestCapture(const Value *V, Function &F, bool ReturnCaptures,
-                          const DominatorTree &DT, CaptureComponents Mask,
-                          unsigned MaxUsesToExplore) {
+std::pair<Instruction *, CaptureResult>
+llvm::FindEarliestCapture(const Value *V, Function &F, const DominatorTree &DT,
+                          CaptureComponents Mask, unsigned MaxUsesToExplore) {
   assert(!isa<GlobalValue>(V) &&
          "It doesn't make sense to ask whether a global is captured.");
 
-  EarliestCaptures CB(ReturnCaptures, F, DT, Mask);
+  EarliestCaptures CB(F, DT, Mask);
   PointerMayBeCaptured(V, &CB, MaxUsesToExplore);
   if (capturesAnything(CB.CC))
     ++NumCapturedBefore;
   else
     ++NumNotCapturedBefore;
-  return {CB.EarliestCapture, CB.CC};
+  return {CB.EarliestCapture, {CB.CC, CB.CCWithRet}};
 }
 
 UseCaptureInfo llvm::DetermineUseCaptureKind(const Use &U, const Value *Base) {

diff  --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 6704fd0ef86e0..319701e7c25f0 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -1165,8 +1165,8 @@ static bool isFuncLocalAndNotCaptured(Value *Arg, const CallBase *CB,
                                       EarliestEscapeAnalysis &EA) {
   const Value *UnderlyingObj = getUnderlyingObject(Arg);
   return isIdentifiedFunctionLocal(UnderlyingObj) &&
-         capturesNothing(
-             EA.getCapturesBefore(UnderlyingObj, CB, /*OrAt*/ true));
+         capturesNothing(EA.getCapturesBefore(UnderlyingObj, CB, /*OrAt=*/true,
+                                              /*ReturnCaptures=*/false));
 }
 
 DSEState::DSEState(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
@@ -1396,8 +1396,8 @@ bool DSEState::isInvisibleToCallerAfterRet(const Value *V, const Value *Ptr,
   }
   auto I = InvisibleToCallerAfterRet.insert({V, false});
   if (I.second && isInvisibleToCallerOnUnwind(V) && isNoAliasCall(V))
-    I.first->second = capturesNothing(PointerMayBeCaptured(
-        V, /*ReturnCaptures=*/true, CaptureComponents::Provenance));
+    I.first->second = capturesNothing(
+        PointerMayBeCaptured(V, CaptureComponents::Provenance).WithRet);
   return I.first->second;
 }
 
@@ -1414,8 +1414,8 @@ bool DSEState::isInvisibleToCallerOnUnwind(const Value *V) {
     // with the killing MemoryDef. But we refrain from doing so for now to
     // limit compile-time and this does not cause any changes to the number
     // of stores removed on a large test set in practice.
-    I.first->second = capturesAnything(PointerMayBeCaptured(
-        V, /*ReturnCaptures=*/false, CaptureComponents::Provenance));
+    I.first->second = capturesAnything(
+        PointerMayBeCaptured(V, CaptureComponents::Provenance).WithoutRet);
   return !I.first->second;
 }
 

diff  --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 5bfd06affdb1f..3a98e86038904 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -3070,8 +3070,8 @@ static Value *isSafeToSpeculateStore(Instruction *I, BasicBlock *BrBB,
         bool ExplicitlyDereferenceableOnly;
         if (isWritableObject(Obj, ExplicitlyDereferenceableOnly) &&
             capturesNothing(
-                PointerMayBeCaptured(Obj, /*ReturnCaptures=*/false,
-                                     CaptureComponents::Provenance)) &&
+                PointerMayBeCaptured(Obj, CaptureComponents::Provenance)
+                    .WithoutRet) &&
             (!ExplicitlyDereferenceableOnly ||
              isDereferenceablePointer(StorePtr, StoreTy,
                                       LI->getDataLayout()))) {


        


More information about the llvm-commits mailing list