[llvm] [FunctionAttrs] Infer noalias through a null check. (PR #226956)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 29 02:01:00 PDT 2026


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

>From 932d393c9cb09693d984d22a51a0a8faa3d0dc4d Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 28 Sep 2026 10:54:49 +0100
Subject: [PATCH 1/4] precommit tests

---
 llvm/test/Transforms/FunctionAttrs/noalias.ll | 154 ++++++++++++++++++
 1 file changed, 154 insertions(+)

diff --git a/llvm/test/Transforms/FunctionAttrs/noalias.ll b/llvm/test/Transforms/FunctionAttrs/noalias.ll
index 25c701bf33f760..d385051d2a4863 100644
--- a/llvm/test/Transforms/FunctionAttrs/noalias.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noalias.ll
@@ -243,3 +243,157 @@ define ptr @return_unknown_noalias_call(ptr %fn) {
   %a = call noalias ptr %fn()
   ret ptr %a
 }
+
+; A null check does not capture the provenance of the result.
+define ptr @return_malloc_null_checked(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_null_checked(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], null
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %c = icmp eq ptr %a, null
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  ret ptr %a
+}
+
+; A null check does not hide the capture by the store to @g.
+define ptr @return_malloc_null_checked_captured(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_null_checked_captured(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], null
+; CHECK-NEXT:    store ptr [[A]], ptr @g, align 8
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %c = icmp eq ptr %a, null
+  store ptr %a, ptr @g
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  ret ptr %a
+}
+
+declare void @capture_address_is_null(ptr captures(address_is_null))
+declare void @capture_provenance(ptr captures(provenance))
+declare void @capture_address_is_null_provenance(ptr captures(address_is_null, provenance))
+declare void @capture_read_provenance(ptr captures(read_provenance))
+
+; Only whether the result is null is captured.
+define ptr @return_malloc_capture_address_is_null(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_capture_address_is_null(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    call void @capture_address_is_null(ptr [[A]])
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  call void @capture_address_is_null(ptr %a)
+  ret ptr %a
+}
+
+; The provenance of the result is captured in addition to the null check.
+define ptr @return_malloc_null_checked_capture_provenance(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_null_checked_capture_provenance(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], null
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    call void @capture_provenance(ptr [[A]])
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %c = icmp eq ptr %a, null
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  call void @capture_provenance(ptr %a)
+  ret ptr %a
+}
+
+; The provenance of the result is captured together with the null check.
+define ptr @return_malloc_capture_address_is_null_provenance(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_capture_address_is_null_provenance(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    call void @capture_address_is_null_provenance(ptr [[A]])
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  call void @capture_address_is_null_provenance(ptr %a)
+  ret ptr %a
+}
+
+; The read provenance of the result is captured in addition to the null check.
+define ptr @return_malloc_null_checked_capture_read_provenance(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_null_checked_capture_read_provenance(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], null
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    call void @capture_read_provenance(ptr [[A]])
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %c = icmp eq ptr %a, null
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  call void @capture_read_provenance(ptr %a)
+  ret ptr %a
+}
+
+; A null check of a GEP of the result captures the address of the result.
+define ptr @return_malloc_gep_null_checked(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_gep_null_checked(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, ptr [[A]], i64 8
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[GEP]], null
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %gep = getelementptr i8, ptr %a, i64 8
+  %c = icmp eq ptr %gep, null
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  ret ptr %a
+}

>From fdc353a74c06a31b27a4f36705fd1252a9103896 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 28 Sep 2026 17:56:03 +0100
Subject: [PATCH 2/4] !fixup add tests capturing address only

---
 llvm/test/Transforms/FunctionAttrs/noalias.ll | 40 ++++++++++++++++++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/llvm/test/Transforms/FunctionAttrs/noalias.ll b/llvm/test/Transforms/FunctionAttrs/noalias.ll
index d385051d2a4863..ac511d7ebbb163 100644
--- a/llvm/test/Transforms/FunctionAttrs/noalias.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noalias.ll
@@ -373,7 +373,7 @@ else:
   ret ptr %a
 }
 
-; A null check of a GEP of the result captures the address of the result.
+; A null check of a GEP does not capture the provenance of the result.
 define ptr @return_malloc_gep_null_checked(i64 %size) {
 ; CHECK-LABEL: define ptr @return_malloc_gep_null_checked(
 ; CHECK-SAME: i64 [[SIZE:%.*]]) {
@@ -397,3 +397,41 @@ then:
 else:
   ret ptr %a
 }
+
+; Comparing the result against another pointer only captures its address.
+define ptr @return_malloc_compared(i64 %size, ptr %p) {
+; CHECK-LABEL: define ptr @return_malloc_compared(
+; CHECK-SAME: i64 [[SIZE:%.*]], ptr nofree readnone captures(address) [[P:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], [[P]]
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[ELSE:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    ret ptr null
+; CHECK:       [[ELSE]]:
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  %c = icmp eq ptr %a, %p
+  br i1 %c, label %then, label %else
+
+then:
+  ret ptr null
+
+else:
+  ret ptr %a
+}
+
+declare void @capture_address(ptr captures(address))
+
+; Only the address of the result is captured.
+define ptr @return_malloc_capture_address(i64 %size) {
+; CHECK-LABEL: define ptr @return_malloc_capture_address(
+; CHECK-SAME: i64 [[SIZE:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
+; CHECK-NEXT:    call void @capture_address(ptr [[A]])
+; CHECK-NEXT:    ret ptr [[A]]
+;
+  %a = call ptr @malloc(i64 %size)
+  call void @capture_address(ptr %a)
+  ret ptr %a
+}

>From e87bb4c44f1919f62ada7ec4639d76078e9579f1 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 25 Jun 2026 18:14:03 +0100
Subject: [PATCH 3/4] [FunctionAttrs] Infer noalias return through a null check
 of the result

Only comparing against null does not captures provenance and should not
impact whether a pointer is noalias or not.

This enables noalias inference in a number of cases for malloc-like
functions: https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1459.
---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     | 6 +++++-
 llvm/test/Transforms/FunctionAttrs/noalias.ll | 4 ++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index a713ead683476e..dc6d3fca137c87 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -1485,7 +1485,11 @@ static bool isFunctionMallocLike(Function *F, const SCCNodeSet &SCCNodes) {
         return false; // Did not come from an allocation.
       }
 
-    if (PointerMayBeCaptured(RetVal, /*ReturnCaptures=*/false))
+    // Checking if result is null does not prevent it from being noalias.
+    if (capturesAnything(
+            PointerMayBeCaptured(RetVal, CaptureComponents::All &
+                                             ~CaptureComponents::AddressIsNull)
+                .WithoutRet))
       return false;
   }
 
diff --git a/llvm/test/Transforms/FunctionAttrs/noalias.ll b/llvm/test/Transforms/FunctionAttrs/noalias.ll
index ac511d7ebbb163..0047906c9b1279 100644
--- a/llvm/test/Transforms/FunctionAttrs/noalias.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noalias.ll
@@ -246,7 +246,7 @@ define ptr @return_unknown_noalias_call(ptr %fn) {
 
 ; A null check does not capture the provenance of the result.
 define ptr @return_malloc_null_checked(i64 %size) {
-; CHECK-LABEL: define ptr @return_malloc_null_checked(
+; CHECK-LABEL: define noalias ptr @return_malloc_null_checked(
 ; CHECK-SAME: i64 [[SIZE:%.*]]) {
 ; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
 ; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], null
@@ -299,7 +299,7 @@ declare void @capture_read_provenance(ptr captures(read_provenance))
 
 ; Only whether the result is null is captured.
 define ptr @return_malloc_capture_address_is_null(i64 %size) {
-; CHECK-LABEL: define ptr @return_malloc_capture_address_is_null(
+; CHECK-LABEL: define noalias ptr @return_malloc_capture_address_is_null(
 ; CHECK-SAME: i64 [[SIZE:%.*]]) {
 ; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
 ; CHECK-NEXT:    call void @capture_address_is_null(ptr [[A]])

>From eeb3e713c4a21483bb086e6607d5bb315da41cd2 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Mon, 28 Sep 2026 13:28:14 +0100
Subject: [PATCH 4/4] Skip only for provenance

---
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     | 6 +++---
 llvm/test/Transforms/FunctionAttrs/noalias.ll | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index dc6d3fca137c87..8e8f9f8c12b5eb 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -1485,10 +1485,10 @@ static bool isFunctionMallocLike(Function *F, const SCCNodeSet &SCCNodes) {
         return false; // Did not come from an allocation.
       }
 
-    // Checking if result is null does not prevent it from being noalias.
+    // Only capturing the provenance of the result prevents it from being
+    // noalias.
     if (capturesAnything(
-            PointerMayBeCaptured(RetVal, CaptureComponents::All &
-                                             ~CaptureComponents::AddressIsNull)
+            PointerMayBeCaptured(RetVal, CaptureComponents::Provenance)
                 .WithoutRet))
       return false;
   }
diff --git a/llvm/test/Transforms/FunctionAttrs/noalias.ll b/llvm/test/Transforms/FunctionAttrs/noalias.ll
index 0047906c9b1279..198b8e788b58c8 100644
--- a/llvm/test/Transforms/FunctionAttrs/noalias.ll
+++ b/llvm/test/Transforms/FunctionAttrs/noalias.ll
@@ -375,7 +375,7 @@ else:
 
 ; A null check of a GEP does not capture the provenance of the result.
 define ptr @return_malloc_gep_null_checked(i64 %size) {
-; CHECK-LABEL: define ptr @return_malloc_gep_null_checked(
+; CHECK-LABEL: define noalias ptr @return_malloc_gep_null_checked(
 ; CHECK-SAME: i64 [[SIZE:%.*]]) {
 ; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
 ; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i8, ptr [[A]], i64 8
@@ -400,7 +400,7 @@ else:
 
 ; Comparing the result against another pointer only captures its address.
 define ptr @return_malloc_compared(i64 %size, ptr %p) {
-; CHECK-LABEL: define ptr @return_malloc_compared(
+; CHECK-LABEL: define noalias ptr @return_malloc_compared(
 ; CHECK-SAME: i64 [[SIZE:%.*]], ptr nofree readnone captures(address) [[P:%.*]]) {
 ; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
 ; CHECK-NEXT:    [[C:%.*]] = icmp eq ptr [[A]], [[P]]
@@ -425,7 +425,7 @@ declare void @capture_address(ptr captures(address))
 
 ; Only the address of the result is captured.
 define ptr @return_malloc_capture_address(i64 %size) {
-; CHECK-LABEL: define ptr @return_malloc_capture_address(
+; CHECK-LABEL: define noalias ptr @return_malloc_capture_address(
 ; CHECK-SAME: i64 [[SIZE:%.*]]) {
 ; CHECK-NEXT:    [[A:%.*]] = call ptr @malloc(i64 [[SIZE]])
 ; CHECK-NEXT:    call void @capture_address(ptr [[A]])



More information about the llvm-commits mailing list