[llvm] [FunctionAttrs] Switch readonly etc inference to use CaptureTracking (PR #201136)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 4 00:36:18 PDT 2026


https://github.com/nikic updated https://github.com/llvm/llvm-project/pull/201136

>From 147be79577a3414158f420501af976101606e571 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Tue, 2 Jun 2026 12:45:49 +0200
Subject: [PATCH] [FunctionAttrs] Switch readonly etc inference to use
 CaptureTracking

Inference of memory attributes on arguments also needs to reason
about indirect accesses via captures and implemented a home-grown
variant of CaptureTracking for that purpose. Switch it to the
shared implementation, which slightly improves precision.
---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     | 113 ++++++------------
 .../Transforms/FunctionAttrs/nocapture.ll     |  12 +-
 2 files changed, 44 insertions(+), 81 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index b09f4f20489a7..3393da6200dca 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -881,110 +881,73 @@ determinePointerAccessAttrs(Argument *A,
 
     Use *U = Worklist.pop_back_val();
     Instruction *I = cast<Instruction>(U->getUser());
+    if (isa<ReturnInst>(I))
+      continue;
 
-    switch (I->getOpcode()) {
-    case Instruction::BitCast:
-    case Instruction::GetElementPtr:
-    case Instruction::PHI:
-    case Instruction::Select:
-    case Instruction::AddrSpaceCast:
-      // The original value is not read/written via this if the new value isn't.
+    UseCaptureInfo Info = DetermineUseCaptureKind(*U, A);
+
+    // FIXME: This should really be part of CaptureTracking, but keep it here
+    // for now due to interference with isEscapeSource().
+    if (auto *CB = dyn_cast<CallBase>(I))
+      if (CB->onlyReadsMemory())
+        Info.UseCC &= CaptureComponents::Address;
+
+    if (capturesAnyProvenance(Info.UseCC)) {
+      // Handle indirect access via captured provenance.
+      if (!capturesReadProvenanceOnly(Info.UseCC))
+        return Attribute::None;
+      IsRead = true;
+    }
+
+    if (capturesAnyProvenance(Info.ResultCC)) {
       for (Use &UU : I->uses())
         if (Visited.insert(&UU).second)
           Worklist.push_back(&UU);
-      break;
+    }
 
-    case Instruction::Call:
-    case Instruction::Invoke: {
-      CallBase &CB = cast<CallBase>(*I);
-      if (CB.isCallee(U)) {
+    if (auto *CB = dyn_cast<CallBase>(I)) {
+      if (CB->isCallee(U)) {
         IsRead = true;
-        // Note that indirect calls do not capture, see comment in
-        // CaptureTracking for context
         continue;
       }
 
       // Given we've explicitly handled the callee operand above, what's left
       // must be a data operand (e.g. argument or operand bundle)
-      const unsigned UseIndex = CB.getDataOperandNo(U);
-
-      // Some intrinsics (for instance ptrmask) do not capture their results,
-      // but return results thas alias their pointer argument, and thus should
-      // be handled like GEP or addrspacecast above.
-      if (isIntrinsicReturningPointerAliasingArgumentWithoutCapturing(
-              &CB, /*MustPreserveOffset=*/false)) {
-        for (Use &UU : CB.uses())
-          if (Visited.insert(&UU).second)
-            Worklist.push_back(&UU);
-      } else if (capturesAnyProvenance(CB.getCaptureInfo(UseIndex))) {
-        if (!CB.onlyReadsMemory())
-          // If the callee can save a copy into other memory, then simply
-          // scanning uses of the call is insufficient.  We have no way
-          // of tracking copies of the pointer through memory to see
-          // if a reloaded copy is written to, thus we must give up.
-          return Attribute::None;
-        // Push users for processing once we finish this one
-        if (!I->getType()->isVoidTy())
-          for (Use &UU : I->uses())
-            if (Visited.insert(&UU).second)
-              Worklist.push_back(&UU);
-      }
+      const unsigned UseIndex = CB->getDataOperandNo(U);
 
-      ModRefInfo ArgMR = CB.getMemoryEffects().getModRef(IRMemLocation::ArgMem);
+      ModRefInfo ArgMR =
+          CB->getMemoryEffects().getModRef(IRMemLocation::ArgMem);
       if (isNoModRef(ArgMR))
         continue;
 
-      if (Function *F = CB.getCalledFunction())
-        if (CB.isArgOperand(U) && UseIndex < F->arg_size() &&
+      if (Function *F = CB->getCalledFunction())
+        if (CB->isArgOperand(U) && UseIndex < F->arg_size() &&
             SCCNodes.count(F->getArg(UseIndex)))
           // This is an argument which is part of the speculative SCC.  Note
           // that only operands corresponding to formal arguments of the callee
           // can participate in the speculation.
-          break;
+          continue;
 
       // The accessors used on call site here do the right thing for calls and
       // invokes with operand bundles.
-      if (CB.doesNotAccessMemory(UseIndex)) {
+      if (CB->doesNotAccessMemory(UseIndex)) {
         /* nop */
-      } else if (!isModSet(ArgMR) || CB.onlyReadsMemory(UseIndex)) {
+      } else if (!isModSet(ArgMR) || CB->onlyReadsMemory(UseIndex)) {
         IsRead = true;
-      } else if (!isRefSet(ArgMR) ||
-                 CB.dataOperandHasImpliedAttr(UseIndex, Attribute::WriteOnly)) {
+      } else if (!isRefSet(ArgMR) || CB->dataOperandHasImpliedAttr(
+                                         UseIndex, Attribute::WriteOnly)) {
         IsWrite = true;
       } else {
         return Attribute::None;
       }
-      break;
-    }
-
-    case Instruction::Load:
-      // Volatile and ordered atomic accesses are modelled as reading and
-      // writing the location.
-      if (!cast<LoadInst>(I)->isUnordered())
-        return Attribute::None;
-
-      IsRead = true;
-      break;
-
-    case Instruction::Store:
-      if (cast<StoreInst>(I)->getValueOperand() == *U)
-        // untrackable capture
-        return Attribute::None;
-
-      // Volatile and ordered atomic accesses are modelled as reading and
-      // writing the location.
-      if (!cast<StoreInst>(I)->isUnordered())
-        return Attribute::None;
-
-      IsWrite = true;
-      break;
-
-    case Instruction::ICmp:
-    case Instruction::Ret:
-      break;
+    } else {
+      // Ignore value operand for stores.
+      if (isa<StoreInst>(I) &&
+          StoreInst::getPointerOperandIndex() != U->getOperandNo())
+        continue;
 
-    default:
-      return Attribute::None;
+      IsRead |= I->mayReadFromMemory();
+      IsWrite |= I->mayWriteToMemory();
     }
   }
 
diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll
index d291d6d2d387d..592d4da38d8e6 100644
--- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll
@@ -1088,7 +1088,7 @@ define i64 @captures_not_ret_only(ptr %p) {
 define i64 @captures_ptrtoaddr_stored(ptr %p) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write, argmem: none, inaccessiblemem: none, target_mem: none)
 ; FNATTRS-LABEL: define noundef i64 @captures_ptrtoaddr_stored
-; FNATTRS-SAME: (ptr captures(address) [[P:%.*]]) #[[ATTR1]] {
+; FNATTRS-SAME: (ptr readnone captures(address) [[P:%.*]]) #[[ATTR1]] {
 ; FNATTRS-NEXT:    [[INT:%.*]] = ptrtoaddr ptr [[P]] to i64
 ; FNATTRS-NEXT:    store i64 [[INT]], ptr @gi, align 8
 ; FNATTRS-NEXT:    ret i64 0
@@ -1109,7 +1109,7 @@ define i64 @captures_ptrtoaddr_stored(ptr %p) {
 define i64 @captures_ptrtoaddr_ret(ptr %p) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
 ; FNATTRS-LABEL: define i64 @captures_ptrtoaddr_ret
-; FNATTRS-SAME: (ptr captures(address) [[P:%.*]]) #[[ATTR0]] {
+; FNATTRS-SAME: (ptr readnone captures(address) [[P:%.*]]) #[[ATTR0]] {
 ; FNATTRS-NEXT:    [[INT:%.*]] = ptrtoaddr ptr [[P]] to i64
 ; FNATTRS-NEXT:    ret i64 [[INT]]
 ;
@@ -1127,7 +1127,7 @@ define i64 @captures_ptrtoaddr_ret(ptr %p) {
 define i64 @captures_ptrtoaddr_ignored(ptr %p) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
 ; FNATTRS-LABEL: define noundef i64 @captures_ptrtoaddr_ignored
-; FNATTRS-SAME: (ptr captures(address) [[P:%.*]]) #[[ATTR0]] {
+; FNATTRS-SAME: (ptr readnone captures(address) [[P:%.*]]) #[[ATTR0]] {
 ; FNATTRS-NEXT:    [[INT:%.*]] = ptrtoaddr ptr [[P]] to i64
 ; FNATTRS-NEXT:    ret i64 0
 ;
@@ -1401,7 +1401,7 @@ define void @assume_nonnull(ptr %p) {
 define void @captures_metadata_address_is_null(ptr %x, ptr %y) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
 ; FNATTRS-LABEL: define void @captures_metadata_address_is_null
-; FNATTRS-SAME: (ptr captures(address_is_null) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
+; FNATTRS-SAME: (ptr readnone captures(address_is_null) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
 ; FNATTRS-NEXT:    store ptr [[X]], ptr [[Y]], align 8, !captures [[META0:![0-9]+]]
 ; FNATTRS-NEXT:    ret void
 ;
@@ -1418,7 +1418,7 @@ define void @captures_metadata_address_is_null(ptr %x, ptr %y) {
 define void @captures_metadata_address(ptr %x, ptr %y) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
 ; FNATTRS-LABEL: define void @captures_metadata_address
-; FNATTRS-SAME: (ptr captures(address) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
+; FNATTRS-SAME: (ptr readnone captures(address) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
 ; FNATTRS-NEXT:    store ptr [[X]], ptr [[Y]], align 8, !captures [[META1:![0-9]+]]
 ; FNATTRS-NEXT:    ret void
 ;
@@ -1435,7 +1435,7 @@ define void @captures_metadata_address(ptr %x, ptr %y) {
 define void @captures_metadata_address_read_provenance(ptr %x, ptr %y) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write)
 ; FNATTRS-LABEL: define void @captures_metadata_address_read_provenance
-; FNATTRS-SAME: (ptr captures(address, read_provenance) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
+; FNATTRS-SAME: (ptr readonly captures(address, read_provenance) [[X:%.*]], ptr writeonly captures(none) initializes((0, 8)) [[Y:%.*]]) #[[ATTR17]] {
 ; FNATTRS-NEXT:    store ptr [[X]], ptr [[Y]], align 8, !captures [[META2:![0-9]+]]
 ; FNATTRS-NEXT:    ret void
 ;



More information about the llvm-commits mailing list