Skip to content

[CaptureTracking] Remove dereferenceable_or_null special case #135613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions llvm/include/llvm/Analysis/CaptureTracking.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,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.
Expand All @@ -171,12 +166,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
Expand Down
33 changes: 2 additions & 31 deletions llvm/lib/Analysis/CaptureTracking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
20 changes: 3 additions & 17 deletions llvm/lib/Transforms/IPO/AttributorAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 1 addition & 8 deletions llvm/lib/Transforms/Scalar/MemCpyOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/Attributor/nocapture-1.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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]] {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attributor results are currently not optimal because it just generally doesn't support captures, tracked in #135610.

; CHECK-NEXT: [[TMP1:%.*]] = icmp eq ptr [[X]], null
; CHECK-NEXT: ret i1 [[TMP1]]
;
Expand Down
26 changes: 13 additions & 13 deletions llvm/test/Transforms/Attributor/nocapture-2.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down Expand Up @@ -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]]
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion llvm/test/Transforms/FunctionAttrs/nocapture.ll
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
;
Expand Down
Loading