[llvm] [Loads] Use willNotFreeBetween() for dereferenceable-at-point (PR #201353)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 3 06:12:10 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Nikita Popov (nikic)

<details>
<summary>Changes</summary>

If dereferenceable-at-point semantics are enabled, use willNotFreeBetween() to check whether frees are known to not occur between the definition point of the value and the context instruction.

We already use this logic for dereferenceable assumptions, this enables it for other dereferenceability fact (under deref-at-point).

---
Full diff: https://github.com/llvm/llvm-project/pull/201353.diff


2 Files Affected:

- (modified) llvm/lib/Analysis/Loads.cpp (+24-3) 
- (modified) llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll (+75) 


``````````diff
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 797d18325c336..1aed126079829 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -130,12 +130,34 @@ static bool isDereferenceableAndAlignedPointer(
   auto IsKnownDeref = [&]() {
     bool CheckForNonNull, CheckForFreed;
     if (!Size.ule(V->getPointerDereferenceableBytes(DL, CheckForNonNull,
-                                                    CheckForFreed)) ||
-        CheckForFreed)
+                                                    CheckForFreed)))
       return false;
     if (CheckForNonNull &&
         !isKnownNonZero(V, SimplifyQuery(DL, DT, AC, CtxI)))
       return false;
+
+    auto *I = dyn_cast<Instruction>(V);
+    if (CheckForFreed) {
+      const Instruction *DefI;
+      if (I) {
+        // We don't want to consider frees by the instruction producing the
+        // pointer, so skip it if we can.
+        if (auto *II = dyn_cast<InvokeInst>(V)) {
+          DefI = &II->getNormalDest()->front();
+        } else if (!I->isTerminator()) {
+          DefI = I->getNextNode();
+        } else {
+          DefI = I;
+        }
+      } else {
+        // For arguments, check frees from the start of the entry block.
+        DefI = &cast<Argument>(V)->getParent()->getEntryBlock().front();
+      }
+
+      if (!willNotFreeBetween(DefI, CtxI))
+        return false;
+    }
+
     // When using something like !dereferenceable on a load, the
     // dereferenceability may only be valid on a specific control-flow path.
     // If the instruction doesn't dominate the context instruction, we're
@@ -144,7 +166,6 @@ static bool isDereferenceableAndAlignedPointer(
     // in which case we don't know if the dereferenceability info still holds.
     // We don't bother handling allocas here, as they aren't speculatable
     // anyway.
-    auto *I = dyn_cast<Instruction>(V);
     if (I && !isa<AllocaInst>(I))
       return CtxI && isValidAssumeForContext(I, CtxI, DT);
     return true;
diff --git a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
index a03a76373f803..f7e69b0c1f04f 100644
--- a/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
+++ b/llvm/test/Analysis/ValueTracking/memory-dereferenceable.ll
@@ -67,6 +67,7 @@ entry:
 ; GLOBAL: %d4_load{{.*}}(unaligned)
 ; POINT-NOT: %d4_load{{.*}}(unaligned)
     %d4_load = load ptr, ptr @globali32ptr, !dereferenceable !0
+    call void @mayfree()
     %load7 = load i32, ptr %d4_load
 
     ; Load from an offset not covered by the dereferenceable portion
@@ -83,6 +84,7 @@ entry:
 ; GLOBAL: %d_or_null_non_null_load{{.*}}(unaligned)
 ; POINT-NOT: %d_or_null_non_null_load{{.*}}(unaligned)
     %d_or_null_non_null_load = load ptr, ptr @globali32ptr, !nonnull !2, !dereferenceable_or_null !0
+    call void @mayfree()
     %load10 = load i32, ptr %d_or_null_non_null_load
 
     ; Loads from aligned arguments
@@ -119,6 +121,7 @@ entry:
     %no_deref_return = call ptr @foo()
     %deref_return = call dereferenceable(32) ptr @foo()
     %deref_and_aligned_return = call dereferenceable(32) align 16 ptr @foo()
+    call void @mayfree()
     %load23 = load i32, ptr %no_deref_return
     %load24 = load i32, ptr %deref_return, align 16
     %load25 = load i32, ptr %deref_and_aligned_return, align 16
@@ -130,8 +133,10 @@ entry:
 ; POINT-NOT: %d4_aligned_load{{.*}}(aligned)
     %d4_unaligned_load = load ptr, ptr @globali32ptr, !dereferenceable !0
     %d4_aligned_load = load ptr, ptr @globali32ptr, !dereferenceable !0, !align !{i64 16}
+    call void @mayfree()
     %load26 = load i32, ptr %d4_unaligned_load, align 16
     %load27 = load i32, ptr %d4_aligned_load, align 16
+
     ret void
 }
 
@@ -311,6 +316,76 @@ if.end:
   ret void
 }
 
+; CHECK-LABEL: 'dereferenceable_arg_freed_between'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_arg_freed_between(ptr dereferenceable(16) %a) {
+  call void @mayfree()
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_arg_freed_between2'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_arg_freed_between2(ptr dereferenceable(16) %a, ptr %p) {
+  load atomic i32, ptr %p acquire, align 4
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_arg_not_freed_between'
+; CHECK: %a
+define void @dereferenceable_arg_not_freed_between(ptr dereferenceable(16) %a) {
+  call void @mayfree() nofree
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_ret_freed_between'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_ret_freed_between() {
+  %a = call dereferenceable(16) ptr @foo()
+  call void @mayfree()
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_ret_freed_between2'
+; GLOBAL: %a
+; POINT-NOT: %a
+define void @dereferenceable_ret_freed_between2(ptr %p) {
+  %a = call dereferenceable(16) ptr @foo()
+  load atomic i32, ptr %p acquire, align 4
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_ret_not_freed_between'
+; CHECK: %a
+define void @dereferenceable_ret_not_freed_between() {
+  %a = call dereferenceable(16) ptr @foo()
+  call void @mayfree() nofree
+  %v = load i32, ptr %a
+  ret void
+}
+
+; CHECK-LABEL: 'dereferenceable_ret_not_freed_between_invoke'
+; CHECK: %a
+define void @dereferenceable_ret_not_freed_between_invoke() personality ptr null {
+  %a = invoke dereferenceable(16) ptr @foo() to label %cont unwind label %lpad
+
+cont:
+  call void @mayfree() nofree
+  %v = load i32, ptr %a
+  ret void
+
+lpad:
+  %l = landingpad { ptr, i32 } cleanup
+  resume { ptr, i32} %l
+}
+
 declare token @llvm.experimental.gc.statepoint.p0(i64, i32, ptr, i32, i32, ...)
 declare ptr addrspace(1) @llvm.experimental.gc.relocate.p1(token, i32, i32)
 

``````````

</details>


https://github.com/llvm/llvm-project/pull/201353


More information about the llvm-commits mailing list