[llvm] [AA] Take read-only provenance captures into account (PR #143097)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 6 02:30:59 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis
Author: Nikita Popov (nikic)
<details>
<summary>Changes</summary>
Update the AA CaptureAnalysis providers to return CaptureComponents, so we can distinguish between full provenance and read-only provenance captures.
Use this to restrict "other" memory effects on call from ModRef to Ref.
Ideally we would also apply the same reasoning for escape sources, but the current API cannot actually convey the necessary information (we can only say NoAlias or MayAlias, not MayAlias but only via Ref).
---
Full diff: https://github.com/llvm/llvm-project/pull/143097.diff
7 Files Affected:
- (modified) llvm/include/llvm/Analysis/AliasAnalysis.h (+9-8)
- (modified) llvm/include/llvm/Analysis/CaptureTracking.h (+4-5)
- (modified) llvm/lib/Analysis/BasicAliasAnalysis.cpp (+51-35)
- (modified) llvm/lib/Analysis/CaptureTracking.cpp (+5-6)
- (modified) llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp (+2-1)
- (modified) llvm/test/Analysis/BasicAA/captures.ll (+1-2)
- (modified) llvm/test/Transforms/GVN/captures.ll (+1-2)
``````````diff
diff --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h
index 0e736b92e550e..7b540617ad699 100644
--- a/llvm/include/llvm/Analysis/AliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/AliasAnalysis.h
@@ -153,19 +153,19 @@ struct LLVM_ABI CaptureAnalysis {
/// true, captures by instruction I itself are also considered.
///
/// If I is nullptr, then captures at any point will be considered.
- virtual bool isNotCapturedBefore(const Value *Object, const Instruction *I,
- bool OrAt) = 0;
+ virtual CaptureComponents
+ getCapturesBefore(const Value *Object, const Instruction *I, bool OrAt) = 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 *, bool, 8> IsCapturedCache;
+ SmallDenseMap<const Value *, CaptureComponents, 8> IsCapturedCache;
public:
- bool isNotCapturedBefore(const Value *Object, const Instruction *I,
- bool OrAt) override;
+ CaptureComponents getCapturesBefore(const Value *Object, const Instruction *I,
+ bool OrAt) override;
};
/// Context-sensitive CaptureAnalysis provider, which computes and caches the
@@ -179,7 +179,8 @@ class LLVM_ABI EarliestEscapeAnalysis final : public CaptureAnalysis {
/// not escape, or nullptr if it never escapes. The "earliest" instruction
/// may be a conservative approximation, e.g. the first instruction in the
/// function is always a legal choice.
- DenseMap<const Value *, Instruction *> EarliestEscapes;
+ DenseMap<const Value *, std::pair<Instruction *, CaptureComponents>>
+ EarliestEscapes;
/// Reverse map from instruction to the objects it is the earliest escape for.
/// This is used for cache invalidation purposes.
@@ -189,8 +190,8 @@ class LLVM_ABI EarliestEscapeAnalysis final : public CaptureAnalysis {
EarliestEscapeAnalysis(DominatorTree &DT, const LoopInfo *LI = nullptr)
: DT(DT), LI(LI) {}
- bool isNotCapturedBefore(const Value *Object, const Instruction *I,
- bool OrAt) override;
+ CaptureComponents getCapturesBefore(const Value *Object, const Instruction *I,
+ bool OrAt) override;
void removeInstruction(Instruction *I);
};
diff --git a/llvm/include/llvm/Analysis/CaptureTracking.h b/llvm/include/llvm/Analysis/CaptureTracking.h
index dd6a7f9b14dc6..f371204b873da 100644
--- a/llvm/include/llvm/Analysis/CaptureTracking.h
+++ b/llvm/include/llvm/Analysis/CaptureTracking.h
@@ -105,11 +105,10 @@ namespace llvm {
// cycle.
//
// Only consider components that are part of \p Mask.
- LLVM_ABI Instruction *FindEarliestCapture(const Value *V, Function &F,
- bool ReturnCaptures,
- const DominatorTree &DT,
- CaptureComponents Mask,
- unsigned MaxUsesToExplore = 0);
+ LLVM_ABI std::pair<Instruction *, CaptureComponents>
+ FindEarliestCapture(const Value *V, Function &F, bool ReturnCaptures,
+ 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 e6675256fd5a0..d1393aa035276 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -196,18 +196,20 @@ static bool areBothVScale(const Value *V1, const Value *V2) {
CaptureAnalysis::~CaptureAnalysis() = default;
-bool SimpleCaptureAnalysis::isNotCapturedBefore(const Value *Object,
- const Instruction *I,
- bool OrAt) {
+CaptureComponents SimpleCaptureAnalysis::getCapturesBefore(const Value *Object,
+ const Instruction *I,
+ bool OrAt) {
if (!isIdentifiedFunctionLocal(Object))
- return false;
+ return CaptureComponents::Provenance;
- auto [CacheIt, Inserted] = IsCapturedCache.insert({Object, false});
+ auto [CacheIt, Inserted] =
+ IsCapturedCache.insert({Object, CaptureComponents::Provenance});
if (!Inserted)
return CacheIt->second;
- bool Ret = !capturesAnything(PointerMayBeCaptured(
- Object, /*ReturnCaptures=*/false, CaptureComponents::Provenance));
+ CaptureComponents Ret = PointerMayBeCaptured(
+ Object, /*ReturnCaptures=*/false, CaptureComponents::Provenance,
+ [](CaptureComponents CC) { return capturesFullProvenance(CC); });
CacheIt->second = Ret;
return Ret;
}
@@ -220,37 +222,44 @@ static bool isNotInCycle(const Instruction *I, const DominatorTree *DT,
!isPotentiallyReachableFromMany(Succs, BB, nullptr, DT, LI);
}
-bool EarliestEscapeAnalysis::isNotCapturedBefore(const Value *Object,
- const Instruction *I,
- bool OrAt) {
+CaptureComponents
+EarliestEscapeAnalysis::getCapturesBefore(const Value *Object,
+ const Instruction *I, bool OrAt) {
if (!isIdentifiedFunctionLocal(Object))
- return false;
+ return CaptureComponents::Provenance;
auto Iter = EarliestEscapes.try_emplace(Object);
if (Iter.second) {
- Instruction *EarliestCapture = FindEarliestCapture(
- Object, *const_cast<Function *>(DT.getRoot()->getParent()),
- /*ReturnCaptures=*/false, DT, CaptureComponents::Provenance);
- if (EarliestCapture)
- Inst2Obj[EarliestCapture].push_back(Object);
+ std::pair<Instruction *, CaptureComponents> EarliestCapture =
+ FindEarliestCapture(
+ Object, *const_cast<Function *>(DT.getRoot()->getParent()),
+ /*ReturnCaptures=*/false, DT, CaptureComponents::Provenance);
+ if (EarliestCapture.first)
+ Inst2Obj[EarliestCapture.first].push_back(Object);
Iter.first->second = EarliestCapture;
}
- // No capturing instruction.
- if (!Iter.first->second)
- return true;
-
- // No context instruction means any use is capturing.
- if (!I)
- return false;
+ auto IsNotCapturedBefore = [&]() {
+ // No capturing instruction.
+ Instruction *CaptureInst = Iter.first->second.first;
+ if (!CaptureInst)
+ return true;
- if (I == Iter.first->second) {
- if (OrAt)
+ // No context instruction means any use is capturing.
+ if (!I)
return false;
- return isNotInCycle(I, &DT, LI);
- }
- return !isPotentiallyReachable(Iter.first->second, I, nullptr, &DT, LI);
+ if (I == CaptureInst) {
+ if (OrAt)
+ return false;
+ return isNotInCycle(I, &DT, LI);
+ }
+
+ return !isPotentiallyReachable(CaptureInst, I, nullptr, &DT, LI);
+ };
+ if (IsNotCapturedBefore())
+ return CaptureComponents::None;
+ return Iter.first->second.second;
}
void EarliestEscapeAnalysis::removeInstruction(Instruction *I) {
@@ -950,9 +959,14 @@ ModRefInfo BasicAAResult::getModRefInfo(const CallBase *Call,
// As an exception, ignore allocas, as setjmp is not required to preserve
// non-volatile stores for them.
if (isModOrRefSet(OtherMR) && !isa<Constant>(Object) && Call != Object &&
- AAQI.CA->isNotCapturedBefore(Object, Call, /*OrAt=*/false) &&
- (isa<AllocaInst>(Object) || !Call->hasFnAttr(Attribute::ReturnsTwice)))
- OtherMR = ModRefInfo::NoModRef;
+ (isa<AllocaInst>(Object) || !Call->hasFnAttr(Attribute::ReturnsTwice))) {
+ CaptureComponents CC =
+ AAQI.CA->getCapturesBefore(Object, Call, /*OrAt=*/false);
+ if (capturesNothing(CC))
+ OtherMR = ModRefInfo::NoModRef;
+ else if (capturesReadProvenanceOnly(CC))
+ OtherMR = ModRefInfo::Ref;
+ }
// Refine the modref info for argument memory. We only bother to do this
// if ArgMR is not a subset of OtherMR, otherwise this won't have an impact
@@ -1618,11 +1632,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) && AAQI.CA->isNotCapturedBefore(
- O2, dyn_cast<Instruction>(O1), /*OrAt*/ true))
+ if (isEscapeSource(O1) &&
+ capturesNothing(AAQI.CA->getCapturesBefore(
+ O2, dyn_cast<Instruction>(O1), /*OrAt*/ true)))
return AliasResult::NoAlias;
- if (isEscapeSource(O2) && AAQI.CA->isNotCapturedBefore(
- O1, dyn_cast<Instruction>(O2), /*OrAt*/ true))
+ if (isEscapeSource(O2) &&
+ capturesNothing(AAQI.CA->getCapturesBefore(
+ O1, dyn_cast<Instruction>(O2), /*OrAt*/ true)))
return AliasResult::NoAlias;
}
diff --git a/llvm/lib/Analysis/CaptureTracking.cpp b/llvm/lib/Analysis/CaptureTracking.cpp
index d08ed17a655e4..076f4176c0219 100644
--- a/llvm/lib/Analysis/CaptureTracking.cpp
+++ b/llvm/lib/Analysis/CaptureTracking.cpp
@@ -249,11 +249,10 @@ bool llvm::PointerMayBeCapturedBefore(const Value *V, bool ReturnCaptures,
capturesAnything, LI, MaxUsesToExplore));
}
-Instruction *llvm::FindEarliestCapture(const Value *V, Function &F,
- bool ReturnCaptures,
- const DominatorTree &DT,
- CaptureComponents Mask,
- unsigned MaxUsesToExplore) {
+std::pair<Instruction *, CaptureComponents>
+llvm::FindEarliestCapture(const Value *V, Function &F, bool ReturnCaptures,
+ const DominatorTree &DT, CaptureComponents Mask,
+ unsigned MaxUsesToExplore) {
assert(!isa<GlobalValue>(V) &&
"It doesn't make sense to ask whether a global is captured.");
@@ -263,7 +262,7 @@ Instruction *llvm::FindEarliestCapture(const Value *V, Function &F,
++NumCapturedBefore;
else
++NumNotCapturedBefore;
- return CB.EarliestCapture;
+ return {CB.EarliestCapture, CB.CC};
}
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 49a0c88922c3e..4a2eb9284a6ea 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -2345,7 +2345,8 @@ bool isFuncLocalAndNotCaptured(Value *Arg, const CallBase *CB,
EarliestEscapeAnalysis &EA) {
const Value *UnderlyingObj = getUnderlyingObject(Arg);
return isIdentifiedFunctionLocal(UnderlyingObj) &&
- EA.isNotCapturedBefore(UnderlyingObj, CB, /*OrAt*/ true);
+ capturesNothing(
+ EA.getCapturesBefore(UnderlyingObj, CB, /*OrAt*/ true));
}
SmallVector<MemoryLocation, 1>
diff --git a/llvm/test/Analysis/BasicAA/captures.ll b/llvm/test/Analysis/BasicAA/captures.ll
index c212a466f8ede..c9ed1ea74be88 100644
--- a/llvm/test/Analysis/BasicAA/captures.ll
+++ b/llvm/test/Analysis/BasicAA/captures.ll
@@ -17,8 +17,7 @@ define void @address_capture() {
; CHECK-LABEL: read_only_capture
; CHECK: MayAlias: i32* %a, i32* %p
-; CHECK: Both ModRef: Ptr: i32* %a <-> %p = call ptr @get_ptr()
-; TODO: The ModRef could be just Ref.
+; CHECK: Just Ref: Ptr: i32* %a <-> %p = call ptr @get_ptr()
define void @read_only_capture() {
%a = alloca i32
call void @capture(ptr captures(address, read_provenance) %a)
diff --git a/llvm/test/Transforms/GVN/captures.ll b/llvm/test/Transforms/GVN/captures.ll
index ae47e92da0f2b..96fce438356c4 100644
--- a/llvm/test/Transforms/GVN/captures.ll
+++ b/llvm/test/Transforms/GVN/captures.ll
@@ -43,8 +43,7 @@ define i32 @read_provenance_capture() {
; CHECK-NEXT: call void @capture(ptr captures(address, read_provenance) [[A]])
; CHECK-NEXT: store i32 1, ptr [[A]], align 4
; CHECK-NEXT: call void @unknown_call()
-; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[A]], align 4
-; CHECK-NEXT: ret i32 [[V]]
+; CHECK-NEXT: ret i32 1
;
%a = alloca i32
call void @capture(ptr captures(address, read_provenance) %a)
``````````
</details>
https://github.com/llvm/llvm-project/pull/143097
More information about the llvm-commits
mailing list