Skip to content

[ValueTracking] Allow dereferenceable(0) to be applied to a null pointer#175913

Merged
philnik777 merged 4 commits intollvm:mainfrom
philnik777:allow_zero_size_in_dereferenceable
Jan 14, 2026
Merged

[ValueTracking] Allow dereferenceable(0) to be applied to a null pointer#175913
philnik777 merged 4 commits intollvm:mainfrom
philnik777:allow_zero_size_in_dereferenceable

Conversation

@philnik777
Copy link
Contributor

dereferenceable(<n>) with n being potentially zero can come up when using an operand bundle with a variable size. Currently this implies that the pointer is non-null, even though [nullptr, nullptr) is a valid range in any programming language I'm aware of. This patch removes this implication and updates the language reference to reflect that dereferenceable with a zero argument is valid.

@philnik777 philnik777 requested a review from nikic as a code owner January 14, 2026 10:15
@llvmbot llvmbot added llvm:ir llvm:analysis Includes value tracking, cost tables and constant folding labels Jan 14, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 14, 2026

@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-llvm-analysis

Author: Nikolas Klauser (philnik777)

Changes

dereferenceable(&lt;n&gt;) with n being potentially zero can come up when using an operand bundle with a variable size. Currently this implies that the pointer is non-null, even though [nullptr, nullptr) is a valid range in any programming language I'm aware of. This patch removes this implication and updates the language reference to reflect that dereferenceable with a zero argument is valid.


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

3 Files Affected:

  • (modified) llvm/docs/LangRef.rst (+2-2)
  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+23-6)
  • (modified) llvm/test/Analysis/ValueTracking/assume.ll (+70)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index be752b42ea412..58dcb7d42a3d1 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1572,8 +1572,8 @@ Currently, only the following parameter attributes are defined:
     dereferenceability (consider a pointer to one element past the end of an
     array), however ``dereferenceable(<n>)`` does imply ``nonnull`` in
     ``addrspace(0)`` (which is the default address space), except if the
-    ``null_pointer_is_valid`` function attribute is present.
-    ``n`` should be a positive number. The pointer should be well defined,
+    ``null_pointer_is_valid`` function attribute is present or ``n == 0``.
+    ``n`` should be a non-negative number. The pointer should be well defined,
     otherwise it is undefined behavior. This means ``dereferenceable(<n>)``
     implies ``noundef``. When used in an assume operand bundle, more restricted
     semantics apply. See  :ref:`assume operand bundles <assume_opbundles>` for
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 3dc321b28a060..be346a9fcdd77 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -834,12 +834,29 @@ static bool isKnownNonZeroFromAssume(const Value *V, const SimplifyQuery &Q) {
         continue;
       if (RetainedKnowledge RK = getKnowledgeFromBundle(
               *I, I->bundle_op_info_begin()[Elem.Index])) {
-        if (RK.WasOn == V &&
-            (RK.AttrKind == Attribute::NonNull ||
-             (RK.AttrKind == Attribute::Dereferenceable &&
-              !NullPointerIsDefined(Q.CxtI->getFunction(),
-                                    V->getType()->getPointerAddressSpace()))) &&
-            isValidAssumeForContext(I, Q.CxtI, Q.DT))
+        if (RK.WasOn != V)
+          continue;
+        bool AssumeImpliesNonNull = [&]() {
+          if (RK.AttrKind == Attribute::NonNull)
+            return true;
+
+          if (RK.AttrKind == Attribute::Dereferenceable) {
+            if (NullPointerIsDefined(Q.CxtI->getFunction(),
+                                     V->getType()->getPointerAddressSpace()))
+              return false;
+
+            if (!RK.IRArgValue)
+              return true;
+
+            if(auto* CI = dyn_cast<ConstantInt>(RK.IRArgValue))
+              return !CI->isZero();
+
+            return false;
+          }
+
+          return false;
+        }();
+        if (AssumeImpliesNonNull && isValidAssumeForContext(I, Q.CxtI, Q.DT))
           return true;
       }
       continue;
diff --git a/llvm/test/Analysis/ValueTracking/assume.ll b/llvm/test/Analysis/ValueTracking/assume.ll
index 298facfa3aa9d..e5f5cdbdd9cfe 100644
--- a/llvm/test/Analysis/ValueTracking/assume.ll
+++ b/llvm/test/Analysis/ValueTracking/assume.ll
@@ -159,3 +159,73 @@ A:
   %6 = phi i32 [ %4, %3 ], [ 0, %A ]
   ret i32 %6
 }
+
+define dso_local i32 @test5(ptr readonly %0, i1 %cond, i32 %bytes) nofree nosync {
+; CHECK-LABEL: @test5(
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 [[BYTES:%.*]]) ]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
+; CHECK:       B:
+; CHECK-NEXT:    br label [[A]]
+; CHECK:       A:
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq ptr [[TMP0]], null
+; CHECK-NEXT:    br i1 [[TMP2]], label [[TMP4:%.*]], label [[TMP6:%.*]]
+; CHECK:       3:
+; CHECK-NEXT:    [[TMP3:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT:    br label [[TMP4]]
+; CHECK:       5:
+; CHECK-NEXT:    [[TMP5:%.*]] = phi i32 [ [[TMP3]], [[TMP6]] ], [ 0, [[A]] ]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  call void @llvm.assume(i1 true) ["dereferenceable"(ptr %0, i32 %bytes)]
+  br i1 %cond, label %A, label %B
+
+B:
+  br label %A
+
+A:
+  %2 = icmp eq ptr %0, null
+  br i1 %2, label %5, label %3
+
+3:                                                ; preds = %1
+  %4 = load i32, ptr %0, align 4
+  br label %5
+
+5:                                                ; preds = %1, %3
+  %6 = phi i32 [ %4, %3 ], [ 0, %A ]
+  ret i32 %6
+}
+
+define dso_local i32 @test6(ptr readonly %0, i1 %cond) nofree nosync {
+; CHECK-LABEL: @test6(
+; CHECK-NEXT:    call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP0:%.*]], i32 0) ]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
+; CHECK:       B:
+; CHECK-NEXT:    br label [[A]]
+; CHECK:       A:
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq ptr [[TMP0]], null
+; CHECK-NEXT:    br i1 [[TMP2]], label [[TMP4:%.*]], label [[TMP6:%.*]]
+; CHECK:       3:
+; CHECK-NEXT:    [[TMP3:%.*]] = load i32, ptr [[TMP0]], align 4
+; CHECK-NEXT:    br label [[TMP4]]
+; CHECK:       5:
+; CHECK-NEXT:    [[TMP5:%.*]] = phi i32 [ [[TMP3]], [[TMP6]] ], [ 0, [[A]] ]
+; CHECK-NEXT:    ret i32 [[TMP5]]
+;
+  call void @llvm.assume(i1 true) ["dereferenceable"(ptr %0, i32 0)]
+  br i1 %cond, label %A, label %B
+
+B:
+  br label %A
+
+A:
+  %2 = icmp eq ptr %0, null
+  br i1 %2, label %5, label %3
+
+3:                                                ; preds = %1
+  %4 = load i32, ptr %0, align 4
+  br label %5
+
+5:                                                ; preds = %1, %3
+  %6 = phi i32 [ %4, %3 ], [ 0, %A ]
+  ret i32 %6
+}

@github-actions
Copy link

github-actions bot commented Jan 14, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

This shouldn't change the spec for the dereferenceable attribute, but add an exception to the assume operand bundle docs in https://llvm.org/docs/LangRef.html#assume-operand-bundles.

dereferenceable(0) is rejected by the IR parser, and I don't see a reason to allow it. Operand bundles are generally more liberal in what they accept, specifically because of non-constant values.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

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

LGTM

@nikic nikic requested a review from fhahn January 14, 2026 10:52
Copy link
Contributor

@fhahn fhahn left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for tracking this down + the fix

@philnik777 philnik777 merged commit d2afc3e into llvm:main Jan 14, 2026
12 checks passed
philnik777 added a commit that referenced this pull request Jan 15, 2026
#175913 removed that `__builtin_assume_dereferenceable(ptr, 0)` implies
`ptr != nullptr`, which should allow us to use the builtin with LLVM 23.

This reverts commit 776c09c.
@philnik777 philnik777 deleted the allow_zero_size_in_dereferenceable branch January 16, 2026 09:06
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
…ter (llvm#175913)

`dereferenceable(<n>)` with n being potentially zero can come up when
using an operand bundle with a variable size. Currently this implies
that the pointer is non-null, even though `[nullptr, nullptr)` is a
valid range in any programming language I'm aware of. This patch removes
this implication and updates the language reference to reflect that
`dereferenceable` with a zero argument is valid.
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
llvm#175913 removed that `__builtin_assume_dereferenceable(ptr, 0)` implies
`ptr != nullptr`, which should allow us to use the builtin with LLVM 23.

This reverts commit 776c09c.
fhahn pushed a commit to fhahn/llvm-project that referenced this pull request Jan 19, 2026
…ter (llvm#175913)

`dereferenceable(<n>)` with n being potentially zero can come up when
using an operand bundle with a variable size. Currently this implies
that the pointer is non-null, even though `[nullptr, nullptr)` is a
valid range in any programming language I'm aware of. This patch removes
this implication and updates the language reference to reflect that
`dereferenceable` with a zero argument is valid.

(cherry picked from commit d2afc3e)
BStott6 pushed a commit to BStott6/llvm-project that referenced this pull request Jan 22, 2026
…ter (llvm#175913)

`dereferenceable(<n>)` with n being potentially zero can come up when
using an operand bundle with a variable size. Currently this implies
that the pointer is non-null, even though `[nullptr, nullptr)` is a
valid range in any programming language I'm aware of. This patch removes
this implication and updates the language reference to reflect that
`dereferenceable` with a zero argument is valid.
BStott6 pushed a commit to BStott6/llvm-project that referenced this pull request Jan 22, 2026
llvm#175913 removed that `__builtin_assume_dereferenceable(ptr, 0)` implies
`ptr != nullptr`, which should allow us to use the builtin with LLVM 23.

This reverts commit 776c09c.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:analysis Includes value tracking, cost tables and constant folding llvm:ir

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants