diff options
author | Nikita Popov <npopov@redhat.com> | 2025-04-17 12:44:57 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-17 12:44:57 +0200 |
commit | d69ee885cccecb49f0b288ec634186c35c8ecfb5 (patch) | |
tree | 2bf91409855aaca21365e305cb9a993ceb693b40 | |
parent | 6f91bfcc8aebe61ba4469c48270928f82ee89027 (diff) | |
download | llvm-d69ee885cccecb49f0b288ec634186c35c8ecfb5.zip llvm-d69ee885cccecb49f0b288ec634186c35c8ecfb5.tar.gz llvm-d69ee885cccecb49f0b288ec634186c35c8ecfb5.tar.bz2 |
[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.
-rw-r--r-- | llvm/include/llvm/Analysis/CaptureTracking.h | 12 | ||||
-rw-r--r-- | llvm/lib/Analysis/CaptureTracking.cpp | 33 | ||||
-rw-r--r-- | llvm/lib/Transforms/IPO/AttributorAttributes.cpp | 20 | ||||
-rw-r--r-- | llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp | 9 | ||||
-rw-r--r-- | llvm/test/Transforms/Attributor/nocapture-1.ll | 2 | ||||
-rw-r--r-- | llvm/test/Transforms/Attributor/nocapture-2.ll | 26 | ||||
-rw-r--r-- | llvm/test/Transforms/FunctionAttrs/nocapture.ll | 2 |
7 files changed, 22 insertions, 82 deletions
diff --git a/llvm/include/llvm/Analysis/CaptureTracking.h b/llvm/include/llvm/Analysis/CaptureTracking.h index bd8d2bb..c0cea8c 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 aa53a27..2550b7a 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 cc6e846..ac56df3 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 0d03395..17a35c6 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 c6097b3..1f1c442 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 475121f..6242a1e 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 dc1fdb6..50dcb74 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]] ; |