[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