[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