[llvm] d69ee88 - [CaptureTracking] Remove dereferenceable_or_null special case (#135613)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 17 03:45:00 PDT 2025
Author: Nikita Popov
Date: 2025-04-17T12:44:57+02:00
New Revision: d69ee885cccecb49f0b288ec634186c35c8ecfb5
URL: https://github.com/llvm/llvm-project/commit/d69ee885cccecb49f0b288ec634186c35c8ecfb5
DIFF: https://github.com/llvm/llvm-project/commit/d69ee885cccecb49f0b288ec634186c35c8ecfb5.diff
LOG: [CaptureTracking] Remove dereferenceable_or_null special case (#135613)
Remove the special case where comparing a dereferenceable_or_null
pointer with null results in captures(none) instead of
captures(address_is_null).
This special case is not entirely correct. Let's say we have an
allocated object of size 2 at address 1 and have a pointer `%p` pointing
either to address 1 or 2. Then passing `gep p, -1` to a
`dereferenceable_or_null(1)` function is well-defined, and allows us to
distinguish between the two possible pointers, capturing information
about the address.
Now that we ignore address captures in alias analysis, I think we're
ready to drop this special case. Additionally, if there are regressions
in other places, the fact that this is inferred as address_is_null
should allow us to easily address them if necessary.
Added:
Modified:
llvm/include/llvm/Analysis/CaptureTracking.h
llvm/lib/Analysis/CaptureTracking.cpp
llvm/lib/Transforms/IPO/AttributorAttributes.cpp
llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
llvm/test/Transforms/Attributor/nocapture-1.ll
llvm/test/Transforms/Attributor/nocapture-2.ll
llvm/test/Transforms/FunctionAttrs/nocapture.ll
Removed:
################################################################################
diff --git a/llvm/include/llvm/Analysis/CaptureTracking.h b/llvm/include/llvm/Analysis/CaptureTracking.h
index bd8d2bb568876..c0cea8c9fadb7 100644
--- a/llvm/include/llvm/Analysis/CaptureTracking.h
+++ b/llvm/include/llvm/Analysis/CaptureTracking.h
@@ -168,11 +168,6 @@ namespace llvm {
/// Return one of Stop, Continue or ContinueIgnoringReturn to control
/// further traversal.
virtual Action captured(const Use *U, UseCaptureInfo CI) = 0;
-
- /// isDereferenceableOrNull - Overload to allow clients with additional
- /// knowledge about pointer dereferenceability to provide it and thereby
- /// avoid conservative responses when a pointer is compared to null.
- virtual bool isDereferenceableOrNull(Value *O, const DataLayout &DL);
};
/// Determine what kind of capture behaviour \p U may exhibit.
@@ -183,12 +178,7 @@ namespace llvm {
///
/// \p Base is the starting value of the capture analysis, which is
/// relevant for address_is_null captures.
- /// The \p IsDereferenceableOrNull callback is used to rule out capturing for
- /// certain comparisons.
- UseCaptureInfo
- DetermineUseCaptureKind(const Use &U, const Value *Base,
- llvm::function_ref<bool(Value *, const DataLayout &)>
- IsDereferenceableOrNull);
+ UseCaptureInfo DetermineUseCaptureKind(const Use &U, const Value *Base);
/// PointerMayBeCaptured - Visit the value and the values derived from it and
/// find values which appear to be capturing the pointer value. This feeds
diff --git a/llvm/lib/Analysis/CaptureTracking.cpp b/llvm/lib/Analysis/CaptureTracking.cpp
index aa53a27537567..2550b7a4732c1 100644
--- a/llvm/lib/Analysis/CaptureTracking.cpp
+++ b/llvm/lib/Analysis/CaptureTracking.cpp
@@ -56,21 +56,6 @@ CaptureTracker::~CaptureTracker() = default;
bool CaptureTracker::shouldExplore(const Use *U) { return true; }
-bool CaptureTracker::isDereferenceableOrNull(Value *O, const DataLayout &DL) {
- // We want comparisons to null pointers to not be considered capturing,
- // but need to guard against cases like gep(p, -ptrtoint(p2)) == null,
- // which are equivalent to p == p2 and would capture the pointer.
- //
- // A dereferenceable pointer is a case where this is known to be safe,
- // because the pointer resulting from such a construction would not be
- // dereferenceable.
- //
- // It is not sufficient to check for inbounds GEP here, because GEP with
- // zero offset is always inbounds.
- bool CanBeNull, CanBeFreed;
- return O->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed);
-}
-
namespace {
struct SimpleCaptureTracker : public CaptureTracker {
explicit SimpleCaptureTracker(bool ReturnCaptures, CaptureComponents Mask,
@@ -281,9 +266,7 @@ Instruction *llvm::FindEarliestCapture(const Value *V, Function &F,
return CB.EarliestCapture;
}
-UseCaptureInfo llvm::DetermineUseCaptureKind(
- const Use &U, const Value *Base,
- function_ref<bool(Value *, const DataLayout &)> IsDereferenceableOrNull) {
+UseCaptureInfo llvm::DetermineUseCaptureKind(const Use &U, const Value *Base) {
Instruction *I = dyn_cast<Instruction>(U.getUser());
// TODO: Investigate non-instruction uses.
@@ -391,15 +374,6 @@ UseCaptureInfo llvm::DetermineUseCaptureKind(
if (U->getType()->getPointerAddressSpace() == 0)
if (isNoAliasCall(U.get()->stripPointerCasts()))
return CaptureComponents::None;
- if (!I->getFunction()->nullPointerIsDefined()) {
- auto *O = I->getOperand(Idx)->stripPointerCastsSameRepresentation();
- // Comparing a dereferenceable_or_null pointer against null cannot
- // lead to pointer escapes, because if it is not null it must be a
- // valid (in-bounds) pointer.
- const DataLayout &DL = I->getDataLayout();
- if (IsDereferenceableOrNull && IsDereferenceableOrNull(O, DL))
- return CaptureComponents::None;
- }
// Check whether this is a comparison of the base pointer against
// null.
@@ -447,12 +421,9 @@ void llvm::PointerMayBeCaptured(const Value *V, CaptureTracker *Tracker,
if (!AddUses(V))
return;
- auto IsDereferenceableOrNull = [Tracker](Value *V, const DataLayout &DL) {
- return Tracker->isDereferenceableOrNull(V, DL);
- };
while (!Worklist.empty()) {
const Use *U = Worklist.pop_back_val();
- UseCaptureInfo CI = DetermineUseCaptureKind(*U, V, IsDereferenceableOrNull);
+ UseCaptureInfo CI = DetermineUseCaptureKind(*U, V);
if (capturesAnything(CI.UseCC)) {
switch (Tracker->captured(U, CI)) {
case CaptureTracker::Stop:
diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index cc6e846f4f211..ac56df3823e20 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -3928,12 +3928,6 @@ struct AANoAliasCallSiteArgument final : AANoAliasImpl {
// (iii) There is no other pointer argument which could alias with the
// value.
- auto IsDereferenceableOrNull = [&](Value *O, const DataLayout &DL) {
- const auto *DerefAA = A.getAAFor<AADereferenceable>(
- *this, IRPosition::value(*O), DepClassTy::OPTIONAL);
- return DerefAA ? DerefAA->getAssumedDereferenceableBytes() : 0;
- };
-
const IRPosition &VIRP = IRPosition::value(getAssociatedValue());
const Function *ScopeFn = VIRP.getAnchorScope();
// Check whether the value is captured in the scope using AANoCapture.
@@ -3973,8 +3967,7 @@ struct AANoAliasCallSiteArgument final : AANoAliasImpl {
// is CGSCC runs. For those we would need to "allow" AANoCapture for
// a value in the module slice.
// TODO(captures): Make this more precise.
- UseCaptureInfo CI =
- DetermineUseCaptureKind(U, /*Base=*/nullptr, IsDereferenceableOrNull);
+ UseCaptureInfo CI = DetermineUseCaptureKind(U, /*Base=*/nullptr);
if (capturesNothing(CI))
return true;
if (CI.isPassthrough()) {
@@ -6035,16 +6028,9 @@ ChangeStatus AANoCaptureImpl::updateImpl(Attributor &A) {
}
}
- auto IsDereferenceableOrNull = [&](Value *O, const DataLayout &DL) {
- const auto *DerefAA = A.getAAFor<AADereferenceable>(
- *this, IRPosition::value(*O), DepClassTy::OPTIONAL);
- return DerefAA && DerefAA->getAssumedDereferenceableBytes();
- };
-
auto UseCheck = [&](const Use &U, bool &Follow) -> bool {
// TODO(captures): Make this more precise.
- UseCaptureInfo CI =
- DetermineUseCaptureKind(U, /*Base=*/nullptr, IsDereferenceableOrNull);
+ UseCaptureInfo CI = DetermineUseCaptureKind(U, /*Base=*/nullptr);
if (capturesNothing(CI))
return true;
if (CI.isPassthrough()) {
@@ -12177,7 +12163,7 @@ struct AAGlobalValueInfoFloating : public AAGlobalValueInfo {
auto UsePred = [&](const Use &U, bool &Follow) -> bool {
Uses.insert(&U);
// TODO(captures): Make this more precise.
- UseCaptureInfo CI = DetermineUseCaptureKind(U, /*Base=*/nullptr, nullptr);
+ UseCaptureInfo CI = DetermineUseCaptureKind(U, /*Base=*/nullptr);
if (CI.isPassthrough()) {
Follow = true;
return true;
diff --git a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
index 0d03395f0ff20..17a35c66c5966 100644
--- a/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
+++ b/llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
@@ -1519,12 +1519,6 @@ bool MemCpyOptPass::performStackMoveOptzn(Instruction *Load, Instruction *Store,
SmallSet<Instruction *, 4> AAMetadataInstrs;
bool SrcNotDom = false;
- // Recursively track the user and check whether modified alias exist.
- auto IsDereferenceableOrNull = [](Value *V, const DataLayout &DL) -> bool {
- bool CanBeNull, CanBeFreed;
- return V->getPointerDereferenceableBytes(DL, CanBeNull, CanBeFreed);
- };
-
auto CaptureTrackingWithModRef =
[&](Instruction *AI,
function_ref<bool(Instruction *)> ModRefCallback) -> bool {
@@ -1551,8 +1545,7 @@ bool MemCpyOptPass::performStackMoveOptzn(Instruction *Load, Instruction *Store,
}
if (!Visited.insert(&U).second)
continue;
- UseCaptureInfo CI =
- DetermineUseCaptureKind(U, AI, IsDereferenceableOrNull);
+ UseCaptureInfo CI = DetermineUseCaptureKind(U, AI);
// TODO(captures): Make this more precise.
if (capturesAnything(CI.UseCC))
return false;
diff --git a/llvm/test/Transforms/Attributor/nocapture-1.ll b/llvm/test/Transforms/Attributor/nocapture-1.ll
index c6097b3868d16..1f1c442fbc5f7 100644
--- a/llvm/test/Transforms/Attributor/nocapture-1.ll
+++ b/llvm/test/Transforms/Attributor/nocapture-1.ll
@@ -741,7 +741,7 @@ define i1 @nocaptureInboundsGEPICmpRev(ptr %x) {
define i1 @nocaptureDereferenceableOrNullICmp(ptr dereferenceable_or_null(4) %x) {
; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define {{[^@]+}}@nocaptureDereferenceableOrNullICmp
-; CHECK-SAME: (ptr nofree noundef readnone captures(none) dereferenceable_or_null(4) [[X:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: (ptr nofree noundef readnone dereferenceable_or_null(4) [[X:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq ptr [[X]], null
; CHECK-NEXT: ret i1 [[TMP1]]
;
diff --git a/llvm/test/Transforms/Attributor/nocapture-2.ll b/llvm/test/Transforms/Attributor/nocapture-2.ll
index 475121f6bd357..6242a1e20a2fe 100644
--- a/llvm/test/Transforms/Attributor/nocapture-2.ll
+++ b/llvm/test/Transforms/Attributor/nocapture-2.ll
@@ -164,14 +164,14 @@ entry:
define ptr @scc_A(ptr dereferenceable_or_null(4) %a) {
; CHECK: Function Attrs: nofree nosync nounwind memory(none)
; CHECK-LABEL: define noundef dereferenceable_or_null(4) ptr @scc_A
-; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) [[A:%.*]]) #[[ATTR2:[0-9]+]] {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null
; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
; CHECK: cond.true:
-; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_C(ptr noalias nofree noundef nonnull readnone dereferenceable(4) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
-; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
-; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_C(ptr noalias nofree noundef nonnull readnone dereferenceable(4) [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(8) [[A]]) #[[ATTR2]]
; CHECK-NEXT: br label [[COND_END:%.*]]
; CHECK: cond.false:
; CHECK-NEXT: br label [[COND_END]]
@@ -201,14 +201,14 @@ cond.end: ; preds = %cond.false, %cond.t
define ptr @scc_B(ptr dereferenceable_or_null(8) %a) {
; CHECK: Function Attrs: nofree nosync nounwind memory(none)
; CHECK-LABEL: define noundef dereferenceable_or_null(8) ptr @scc_B
-; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(8) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR2]] {
+; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(8) [[A:%.*]]) #[[ATTR2]] {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null
; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
; CHECK: cond.true:
-; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_A(ptr noalias nofree noundef nonnull readnone dereferenceable(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
-; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
-; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_A(ptr noalias nofree noundef nonnull readnone dereferenceable(8) [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL1:%.*]] = call dereferenceable_or_null(8) ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(8) [[A]]) #[[ATTR2]]
; CHECK-NEXT: br label [[COND_END:%.*]]
; CHECK: cond.false:
; CHECK-NEXT: br label [[COND_END]]
@@ -237,20 +237,20 @@ cond.end: ; preds = %cond.false, %cond.t
define ptr @scc_C(ptr dereferenceable_or_null(2) %a) {
; CHECK: Function Attrs: nofree nosync nounwind memory(none)
; CHECK-LABEL: define noundef dereferenceable_or_null(4) ptr @scc_C
-; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) "no-capture-maybe-returned" [[A:%.*]]) #[[ATTR2]] {
+; CHECK-SAME: (ptr nofree noundef readnone returned dereferenceable_or_null(4) [[A:%.*]]) #[[ATTR2]] {
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[CALL:%.*]] = call dereferenceable_or_null(4) ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) [[A]]) #[[ATTR2]]
; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne ptr [[A]], null
; CHECK-NEXT: br i1 [[TOBOOL]], label [[COND_TRUE:%.*]], label [[COND_FALSE:%.*]]
; CHECK: cond.true:
-; CHECK-NEXT: [[CALL1:%.*]] = call ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL1:%.*]] = call ptr @scc_B(ptr noalias nofree noundef readnone dereferenceable_or_null(8) [[A]]) #[[ATTR2]]
; CHECK-NEXT: br label [[COND_END:%.*]]
; CHECK: cond.false:
-; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL2:%.*]] = call ptr @scc_C(ptr noalias nofree noundef readnone dereferenceable_or_null(4) [[A]]) #[[ATTR2]]
; CHECK-NEXT: br label [[COND_END]]
; CHECK: cond.end:
; CHECK-NEXT: [[COND:%.*]] = phi ptr [ [[A]], [[COND_TRUE]] ], [ [[A]], [[COND_FALSE]] ]
-; CHECK-NEXT: [[CALL3:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) "no-capture-maybe-returned" [[A]]) #[[ATTR2]]
+; CHECK-NEXT: [[CALL3:%.*]] = call ptr @scc_A(ptr noalias nofree noundef readnone dereferenceable_or_null(4) [[A]]) #[[ATTR2]]
; CHECK-NEXT: ret ptr [[A]]
;
entry:
diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll
index dc1fdb6100aeb..50dcb74d973a5 100644
--- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll
+++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll
@@ -960,7 +960,7 @@ define i1 @inboundsGEPICmpNullPointerDefined(ptr %x) null_pointer_is_valid {
define i1 @nocaptureDereferenceableOrNullICmp(ptr dereferenceable_or_null(4) %x) {
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
; FNATTRS-LABEL: define noundef i1 @nocaptureDereferenceableOrNullICmp
-; FNATTRS-SAME: (ptr readnone captures(none) dereferenceable_or_null(4) [[X:%.*]]) #[[ATTR0]] {
+; FNATTRS-SAME: (ptr readnone captures(address_is_null) dereferenceable_or_null(4) [[X:%.*]]) #[[ATTR0]] {
; FNATTRS-NEXT: [[TMP1:%.*]] = icmp eq ptr [[X]], null
; FNATTRS-NEXT: ret i1 [[TMP1]]
;
More information about the llvm-commits
mailing list