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

via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 06:47:53 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Nikita Popov (nikic)

<details>
<summary>Changes</summary>

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.

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


5 Files Affected:

- (modified) llvm/include/llvm/Analysis/CaptureTracking.h (+13-6) 
- (modified) llvm/lib/Analysis/BasicAliasAnalysis.cpp (+9-7) 
- (modified) llvm/lib/Analysis/CaptureTracking.cpp (+37-36) 
- (modified) llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp (+4-4) 
- (modified) llvm/lib/Transforms/Utils/SimplifyCFG.cpp (+2-2) 


``````````diff
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..65ffa34eeb923 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -208,9 +208,11 @@ CaptureComponents SimpleCaptureAnalysis::getCapturesBefore(const Value *Object,
   if (!Inserted)
     return CacheIt->second;
 
-  CaptureComponents Ret = PointerMayBeCaptured(
-      Object, /*ReturnCaptures=*/false, CaptureComponents::Provenance,
-      [](CaptureComponents CC) { return capturesFullProvenance(CC); });
+  CaptureComponents Ret =
+      PointerMayBeCaptured(
+          Object, CaptureComponents::Provenance,
+          [](CaptureComponents CC) { return capturesFullProvenance(CC); })
+          .WithoutRet;
   CacheIt->second = Ret;
   return Ret;
 }
@@ -234,13 +236,13 @@ EarliestEscapeAnalysis::getCapturesBefore(const Value *Object,
 
   auto Iter = EarliestEscapes.try_emplace(Object);
   if (Iter.second) {
-    std::pair<Instruction *, CaptureComponents> EarliestCapture =
-        FindEarliestCapture(Object, *DT.getRoot()->getParent(),
-                            /*ReturnCaptures=*/false, DT,
+    std::pair<Instruction *, CaptureResult> EarliestCapture =
+        FindEarliestCapture(Object, *DT.getRoot()->getParent(), DT,
                             CaptureComponents::Provenance);
     if (EarliestCapture.first)
       Inst2Obj[EarliestCapture.first].push_back(Object);
-    Iter.first->second = EarliestCapture;
+    Iter.first->second = {EarliestCapture.first,
+                          EarliestCapture.second.WithoutRet};
   }
 
   auto IsNotCapturedBefore = [&]() {
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..056f844b3805a 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -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()))) {

``````````

</details>


https://github.com/llvm/llvm-project/pull/195857


More information about the llvm-commits mailing list