[llvm] [AssumeBundles] Dereferenceable used in bundle only apply at assume. (PR #126117)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 6 12:47:40 PST 2025


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/126117

>From efda984ec68d2704ebda8bd43e399018207390f7 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 6 Feb 2025 19:27:29 +0000
Subject: [PATCH] [AssumeBundles] Dereferenceable used in bundle only apply at
 assume.

---
 llvm/docs/LangRef.rst                         |  6 +-
 llvm/lib/Analysis/Loads.cpp                   |  4 +-
 llvm/lib/Analysis/ValueTracking.cpp           |  2 +-
 .../ValueTracking/assume-queries-counter.ll   |  2 +-
 llvm/test/Analysis/ValueTracking/assume.ll    |  4 +-
 .../LICM/hoist-speculatable-load.ll           | 56 ++++++++++++++-
 .../SimplifyCFG/speculate-derefable-load.ll   | 68 +++++++++++++++++--
 7 files changed, 129 insertions(+), 13 deletions(-)

diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 8891aedcb58e552..f4d6affab8f9c3e 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1474,7 +1474,11 @@ Currently, only the following parameter attributes are defined:
     ``null_pointer_is_valid`` function attribute is present.
     ``n`` should be a positive number. The pointer should be well defined,
     otherwise it is undefined behavior. This means ``dereferenceable(<n>)``
-    implies ``noundef``.
+    implies ``noundef``. When ``dereferenceable(<n>)`` is used in an
+    :ref:`assume operand bundls <assume_opbundles>`, the pointer is only
+    guaranteed to be dereferenceable at the point of the assumption and
+    may not be dereferenceable at later pointers, e.g. because it could have
+    been freed.
 
 ``dereferenceable_or_null(<n>)``
     This indicates that the parameter or return value isn't both
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 733a7988e5a730a..91ae944d2e68366 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -26,8 +26,6 @@
 
 using namespace llvm;
 
-extern cl::opt<bool> UseDerefAtPointSemantics;
-
 static bool isAligned(const Value *Base, Align Alignment,
                       const DataLayout &DL) {
   return Base->getPointerAlignment(DL) >= Alignment;
@@ -171,7 +169,7 @@ static bool isDereferenceableAndAlignedPointer(
                                               Size, DL, CtxI, AC, DT, TLI,
                                               Visited, MaxDepth);
 
-  if (CtxI && (!UseDerefAtPointSemantics || !V->canBeFreed())) {
+  if (CtxI && !V->canBeFreed()) {
     /// Look through assumes to see if both dereferencability and alignment can
     /// be proven by an assume if needed.
     RetainedKnowledge AlignRK;
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 6eba6c0f08c3f40..eac11ca379932ba 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -626,7 +626,7 @@ static bool isKnownNonZeroFromAssume(const Value *V, const SimplifyQuery &Q) {
               *I, I->bundle_op_info_begin()[Elem.Index])) {
         if (RK.WasOn == V &&
             (RK.AttrKind == Attribute::NonNull ||
-             (RK.AttrKind == Attribute::Dereferenceable &&
+             (RK.AttrKind == Attribute::Dereferenceable && !V->canBeFreed() &&
               !NullPointerIsDefined(Q.CxtI->getFunction(),
                                     V->getType()->getPointerAddressSpace()))) &&
             isValidAssumeForContext(I, Q.CxtI, Q.DT))
diff --git a/llvm/test/Analysis/ValueTracking/assume-queries-counter.ll b/llvm/test/Analysis/ValueTracking/assume-queries-counter.ll
index a53b90b7b448ae8..0223ab8c4b677ab 100644
--- a/llvm/test/Analysis/ValueTracking/assume-queries-counter.ll
+++ b/llvm/test/Analysis/ValueTracking/assume-queries-counter.ll
@@ -47,7 +47,7 @@ define dso_local i1 @test2(ptr readonly %0) {
   ret i1 %2
 }
 
-define dso_local i32 @test4(ptr readonly %0, i1 %cond) {
+define dso_local i32 @test4(ptr readonly %0, i1 %cond) nofree nosync {
 ; COUNTER1-LABEL: @test4(
 ; COUNTER1-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4) ]
 ; COUNTER1-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
diff --git a/llvm/test/Analysis/ValueTracking/assume.ll b/llvm/test/Analysis/ValueTracking/assume.ll
index 4c4b46c41996868..298facfa3aa9d05 100644
--- a/llvm/test/Analysis/ValueTracking/assume.ll
+++ b/llvm/test/Analysis/ValueTracking/assume.ll
@@ -57,7 +57,7 @@ define dso_local i1 @test2(ptr readonly %0) {
   ret i1 %2
 }
 
-define dso_local i32 @test4(ptr readonly %0, i1 %cond) {
+define dso_local i32 @test4(ptr readonly %0, i1 %cond) nofree nosync {
 ; CHECK-LABEL: @test4(
 ; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4) ]
 ; CHECK-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
@@ -91,7 +91,7 @@ A:
   ret i32 %6
 }
 
-define dso_local i32 @test4a(ptr readonly %0, i1 %cond) {
+define dso_local i32 @test4a(ptr readonly %0, i1 %cond) nofree nosync {
 ; CHECK-LABEL: @test4a(
 ; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 4), "align"(ptr [[TMP0]], i32 8) ]
 ; CHECK-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
diff --git a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
index 752cf3e99cbf5a6..cf43ff9913813e3 100644
--- a/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
+++ b/llvm/test/Transforms/LICM/hoist-speculatable-load.ll
@@ -11,12 +11,12 @@ define void @f(i32 %ptr_i, ptr %ptr2, i1 %cond) {
 ; CHECK-NEXT:    store i32 0, ptr [[PTR2:%.*]], align 4
 ; CHECK-NEXT:    br label [[FOR_BODY_LR_PH]]
 ; CHECK:       for.body.lr.ph:
-; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4
 ; CHECK-NEXT:    br label [[FOR_BODY:%.*]]
 ; CHECK:       for.body:
 ; CHECK-NEXT:    [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
 ; CHECK-NEXT:    br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
 ; CHECK:       if:
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4, !invariant.load [[META0:![0-9]+]]
 ; CHECK-NEXT:    store i32 [[TMP0]], ptr [[PTR2]], align 4
 ; CHECK-NEXT:    br label [[IF_END]]
 ; CHECK:       if.end:
@@ -56,4 +56,58 @@ exit:                                         ; preds = %if.end, %entry
   ret void
 }
 
+define void @f_nofree_nosync(i32 %ptr_i, ptr %ptr2, i1 %cond) nofree nosync {
+; CHECK-LABEL: @f_nofree_nosync(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[PTR:%.*]] = inttoptr i32 [[PTR_I:%.*]] to ptr
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "align"(ptr [[PTR]], i32 16), "dereferenceable"(ptr [[PTR]], i32 16) ]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[FOR_BODY_LR_PH:%.*]], label [[IF0:%.*]]
+; CHECK:       if0:
+; CHECK-NEXT:    store i32 0, ptr [[PTR2:%.*]], align 4
+; CHECK-NEXT:    br label [[FOR_BODY_LR_PH]]
+; CHECK:       for.body.lr.ph:
+; CHECK-NEXT:    br label [[FOR_BODY:%.*]]
+; CHECK:       for.body:
+; CHECK-NEXT:    [[I_08:%.*]] = phi i32 [ 0, [[FOR_BODY_LR_PH]] ], [ [[INC:%.*]], [[IF_END:%.*]] ]
+; CHECK-NEXT:    br i1 [[COND]], label [[IF_END]], label [[IF:%.*]]
+; CHECK:       if:
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[PTR]], align 4, !invariant.load [[META0]]
+; CHECK-NEXT:    store i32 [[TMP0]], ptr [[PTR2]], align 4
+; CHECK-NEXT:    br label [[IF_END]]
+; CHECK:       if.end:
+; CHECK-NEXT:    [[INC]] = add nuw nsw i32 [[I_08]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[INC]], 2
+; CHECK-NEXT:    br i1 [[CMP]], label [[FOR_BODY]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %ptr = inttoptr i32 %ptr_i to ptr
+  call void @llvm.assume(i1 true) [ "align"(ptr %ptr, i32 16), "dereferenceable"(ptr %ptr, i32 16) ]
+  br i1 %cond, label %for.body.lr.ph, label %if0
+
+if0:
+  store i32 0, ptr %ptr2, align 4
+  br label %for.body.lr.ph
+
+for.body.lr.ph:                                   ; preds = %entry
+  br label %for.body
+
+for.body:                                         ; preds = %for.body.lr.ph, %if.end
+  %i.08 = phi i32 [ 0, %for.body.lr.ph ], [ %inc, %if.end ]
+  br i1 %cond, label %if.end, label %if
+
+if:
+  %0 = load i32, ptr %ptr, align 4, !invariant.load !{}
+  store i32 %0, ptr %ptr2, align 4
+  br label %if.end
+
+if.end:                                           ; preds = %for.body
+  %inc = add nuw nsw i32 %i.08, 1
+  %cmp = icmp slt i32 %inc, 2
+  br i1 %cmp, label %for.body, label %exit
+
+exit:                                         ; preds = %if.end, %entry
+  ret void
+}
 declare void @llvm.assume(i1 noundef)
diff --git a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
index 0138433312ed84a..b8c999d700aa7da 100644
--- a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
+++ b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
@@ -4,10 +4,14 @@
 define i64 @align_deref_align(i1 %c, ptr %p) {
 ; CHECK-LABEL: define i64 @align_deref_align(
 ; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
+; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF]]:
 ; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P]], align 8
-; CHECK-NEXT:    [[RES:%.*]] = select i1 [[C]], i64 [[V]], i64 0
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    [[RES:%.*]] = phi i64 [ [[V]], %[[IF]] ], [ 0, %[[ENTRY]] ]
 ; CHECK-NEXT:    ret i64 [[RES]]
 ;
 entry:
@@ -23,9 +27,64 @@ exit:
   ret i64 %res
 }
 
+define i64 @align_deref_align_nofree_nosync(i1 %c, ptr %p) nofree nosync {
+; CHECK-LABEL: define i64 @align_deref_align_nofree_nosync(
+; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P]], align 8
+; CHECK-NEXT:    [[SPEC_SELECT:%.*]] = select i1 [[C]], i64 [[V]], i64 0
+; CHECK-NEXT:    ret i64 [[SPEC_SELECT]]
+;
+entry:
+  call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 8), "align"(ptr %p, i64 8) ]
+  br i1 %c, label %if, label %exit
+
+if:
+  %v = load i64, ptr %p, align 8
+  br label %exit
+
+exit:
+  %res = phi i64 [ %v, %if ], [ 0, %entry ]
+  ret i64 %res
+}
+
 define i64 @assume_deref_align2(i1 %c1, i32 %x, ptr %p) {
 ; CHECK-LABEL: define i64 @assume_deref_align2(
 ; CHECK-SAME: i1 [[C1:%.*]], i32 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
+; CHECK-NEXT:    br i1 [[C1]], label %[[IF1:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF1]]:
+; CHECK-NEXT:    [[C2:%.*]] = icmp ugt i32 [[X]], 10
+; CHECK-NEXT:    br i1 [[C2]], label %[[IF2:.*]], label %[[EXIT]]
+; CHECK:       [[IF2]]:
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P]], align 8
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    [[RES:%.*]] = phi i64 [ [[V]], %[[IF2]] ], [ 1, %[[IF1]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i64 [[RES]]
+;
+entry:
+  call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 8), "align"(ptr %p, i64 8) ]
+  br i1 %c1, label %if1, label %exit
+
+if1:
+  %c2 = icmp ugt i32 %x, 10
+  br i1 %c2, label %if2, label %exit
+
+if2:
+  %v = load i64, ptr %p, align 8
+  br label %exit
+
+exit:
+  %res = phi i64 [ %v, %if2 ], [ 1, %if1 ], [ 0, %entry ]
+  ret i64 %res
+}
+
+define i64 @assume_deref_align2_nofree_nosync(i1 %c1, i32 %x, ptr %p) nofree nosync {
+; CHECK-LABEL: define i64 @assume_deref_align2_nofree_nosync(
+; CHECK-SAME: i1 [[C1:%.*]], i32 [[X:%.*]], ptr [[P:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[P]], i64 8), "align"(ptr [[P]], i64 8) ]
 ; CHECK-NEXT:    [[C2:%.*]] = icmp ugt i32 [[X]], 10
@@ -51,9 +110,10 @@ exit:
   ret i64 %res
 }
 
-define i64 @assume_deref_align_not_dominating(i1 %c, ptr %p) {
+
+define i64 @assume_deref_align_not_dominating(i1 %c, ptr %p) nofree nosync {
 ; CHECK-LABEL: define i64 @assume_deref_align_not_dominating(
-; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) {
+; CHECK-SAME: i1 [[C:%.*]], ptr [[P:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
 ; CHECK:       [[IF]]:



More information about the llvm-commits mailing list