https://github.com/tommat01 updated 
https://github.com/llvm/llvm-project/pull/218453

>From 991da4e536bafff72dd53fbbe4543a14937c8332 Mon Sep 17 00:00:00 2001
From: Tomas Matheson <[email protected]>
Date: Mon, 24 Aug 2026 11:07:18 +0100
Subject: [PATCH 1/4] [Attributor] Check callback broker argument aliases
 correctly

AbstractCallSite uses separate callback argument and broker operand numbers. 
AANoAliasCallSiteArgument currently skips the callback argument number while 
iterating broker operands, which can skip an unrelated operand and incorrectly 
infer noalias for aliased callback arguments.

Skip the mapped call-site operand instead. Add a duplicated-operand regression 
where the callback and broker argument numbers differ.
---
 .../Transforms/IPO/AttributorAttributes.cpp   |  2 +-
 .../Transforms/Attributor/callback-noalias.ll | 33 ++++++++++++
 llvm/test/Transforms/Attributor/callbacks.ll  | 54 ++++++++++---------
 3 files changed, 62 insertions(+), 27 deletions(-)
 create mode 100644 llvm/test/Transforms/Attributor/callback-noalias.ll

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp 
b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 8d16f49525ca8..f30395679a51d 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -3907,7 +3907,7 @@ struct AANoAliasCallSiteArgument final : AANoAliasImpl {
                             const AAMemoryBehavior &MemBehaviorAA,
                             const CallBase &CB, unsigned OtherArgNo) {
     // We do not need to worry about aliasing with the underlying IRP.
-    if (this->getCalleeArgNo() == (int)OtherArgNo)
+    if (this->getCallSiteArgNo() == (int)OtherArgNo)
       return false;
 
     // If it is not a pointer or pointer vector we do not alias.
diff --git a/llvm/test/Transforms/Attributor/callback-noalias.ll 
b/llvm/test/Transforms/Attributor/callback-noalias.ll
new file mode 100644
index 0000000000000..aecd2a062a3a4
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/callback-noalias.ll
@@ -0,0 +1,33 @@
+; RUN: opt -passes=attributor -S < %s | FileCheck %s
+; RUN: opt -passes=attributor-cgscc -S < %s | FileCheck %s
+
+; A callback argument number is not necessarily the corresponding broker
+; operand number. Make sure the alias comparison skips the actual call-site
+; operand, rather than an unrelated mapped argument.
+
+; CHECK-LABEL: define internal void @callback(
+; CHECK-SAME: ptr nofree noundef nonnull writeonly align 8 captures(none) 
dereferenceable(8) %a,
+; CHECK-SAME: ptr nofree noundef nonnull readonly align 8 captures(none) 
dereferenceable(8) %b)
+
+define void @caller(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void @broker(ptr @callback, ptr %slot, ptr %slot)
+  ret void
+}
+
+define internal void @callback(ptr align 8 dereferenceable(8) %a,
+                               ptr align 8 dereferenceable(8) %b) {
+  store i64 0, ptr %a, align 8
+  call void @sync()
+  %value = load i64, ptr %b, align 8
+  call void @use(i64 %value)
+  ret void
+}
+
+declare !callback !0 void @broker(ptr, ptr, ptr)
+declare void @sync()
+declare void @use(i64) memory(none)
+
+!0 = !{!1}
+!1 = !{i64 0, i64 1, i64 2, i1 false}
diff --git a/llvm/test/Transforms/Attributor/callbacks.ll 
b/llvm/test/Transforms/Attributor/callbacks.ll
index 80a0b2befbbee..884496e1dab9a 100644
--- a/llvm/test/Transforms/Attributor/callbacks.ll
+++ b/llvm/test/Transforms/Attributor/callbacks.ll
@@ -53,7 +53,7 @@ define internal void @t0_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; TUNIT-LABEL: define {{[^@]+}}@t0_callback_callee
 ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 [[A:%.*]], 
i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 
captures(none) dereferenceable(8) [[C:%.*]]) {
 ; TUNIT-NEXT:  entry:
-; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0:![0-9]+]]
 ; TUNIT-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
 ; TUNIT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
 ; TUNIT-NEXT:    tail call void @t0_check(ptr align 256 [[A]], i64 noundef 99, 
ptr align 32 [[TMP0]])
@@ -62,9 +62,9 @@ define internal void @t0_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; CGSCC-LABEL: define {{[^@]+}}@t0_callback_callee
 ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 [[A:%.*]], 
i64 [[B:%.*]], ptr noalias nofree noundef nonnull readonly align 64 
captures(none) dereferenceable(8) [[C:%.*]]) {
 ; CGSCC-NEXT:  entry:
-; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0:![0-9]+]]
 ; CGSCC-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
-; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
+; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load 
[[META0]]
 ; CGSCC-NEXT:    tail call void @t0_check(ptr align 256 [[A]], i64 noundef 99, 
ptr [[TMP0]])
 ; CGSCC-NEXT:    ret void
 ;
@@ -95,7 +95,7 @@ define void @t1_caller(ptr noalias %a) {
 ; TUNIT-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; TUNIT-NEXT:    store i32 42, ptr [[B]], align 32
 ; TUNIT-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t1_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias 
nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) 
[[C]])
+; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t1_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; TUNIT-NEXT:    ret void
 ;
 ; CGSCC-LABEL: define {{[^@]+}}@t1_caller
@@ -106,7 +106,7 @@ define void @t1_caller(ptr noalias %a) {
 ; CGSCC-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; CGSCC-NEXT:    store i32 42, ptr [[B]], align 32
 ; CGSCC-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t1_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
+; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t1_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t1_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 
99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; CGSCC-NEXT:    ret void
 ;
 entry:
@@ -125,9 +125,9 @@ define internal void @t1_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ;
 ; TUNIT: Function Attrs: nosync
 ; TUNIT-LABEL: define {{[^@]+}}@t1_callback_callee
-; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) 
#[[ATTR0:[0-9]+]] {
+; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr noalias align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) 
#[[ATTR0:[0-9]+]] {
 ; TUNIT-NEXT:  entry:
-; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; TUNIT-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
 ; TUNIT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
 ; TUNIT-NEXT:    tail call void @t1_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr align 32 captures(none) [[TMP0]])
@@ -135,11 +135,11 @@ define internal void @t1_callback_callee(ptr 
%is_not_null, ptr %ptr, ptr %a, i64
 ;
 ; CGSCC: Function Attrs: nosync
 ; CGSCC-LABEL: define {{[^@]+}}@t1_callback_callee
-; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) 
#[[ATTR0:[0-9]+]] {
+; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr noalias align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) 
#[[ATTR0:[0-9]+]] {
 ; CGSCC-NEXT:  entry:
-; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; CGSCC-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
-; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
+; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load 
[[META0]]
 ; CGSCC-NEXT:    tail call void @t1_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr captures(none) [[TMP0]])
 ; CGSCC-NEXT:    ret void
 ;
@@ -168,7 +168,7 @@ define void @t2_caller(ptr noalias %a) {
 ; TUNIT-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; TUNIT-NEXT:    store i32 42, ptr [[B]], align 32
 ; TUNIT-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t2_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias 
nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) 
[[C]])
+; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t2_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; TUNIT-NEXT:    ret void
 ;
 ; CGSCC-LABEL: define {{[^@]+}}@t2_caller
@@ -179,7 +179,7 @@ define void @t2_caller(ptr noalias %a) {
 ; CGSCC-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; CGSCC-NEXT:    store i32 42, ptr [[B]], align 32
 ; CGSCC-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t2_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
+; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t2_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t2_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 
99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; CGSCC-NEXT:    ret void
 ;
 entry:
@@ -201,7 +201,7 @@ define internal void @t2_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; TUNIT-LABEL: define {{[^@]+}}@t2_callback_callee
 ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) {
 ; TUNIT-NEXT:  entry:
-; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; TUNIT-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
 ; TUNIT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
 ; TUNIT-NEXT:    tail call void @t2_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr align 32 captures(none) [[TMP0]])
@@ -210,9 +210,9 @@ define internal void @t2_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; CGSCC-LABEL: define {{[^@]+}}@t2_callback_callee
 ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) {
 ; CGSCC-NEXT:  entry:
-; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; CGSCC-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
-; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
+; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load 
[[META0]]
 ; CGSCC-NEXT:    tail call void @t2_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr captures(none) [[TMP0]])
 ; CGSCC-NEXT:    ret void
 ;
@@ -241,8 +241,8 @@ define void @t3_caller(ptr noalias %a) {
 ; TUNIT-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; TUNIT-NEXT:    store i32 42, ptr [[B]], align 32
 ; TUNIT-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias 
nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) 
[[C]])
-; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr align 256 captures(none) [[A]], i64 undef, ptr noalias 
nofree noundef nonnull readonly align 64 captures(none) dereferenceable(8) 
[[C]])
+; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
+; TUNIT-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 undef, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; TUNIT-NEXT:    ret void
 ;
 ; CGSCC-LABEL: define {{[^@]+}}@t3_caller
@@ -253,8 +253,8 @@ define void @t3_caller(ptr noalias %a) {
 ; CGSCC-NEXT:    [[PTR:%.*]] = alloca i32, align 128
 ; CGSCC-NEXT:    store i32 42, ptr [[B]], align 32
 ; CGSCC-NEXT:    store ptr [[B]], ptr [[C]], align 64
-; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
-; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr align 256 captures(none) [[A]], i64 noundef 99, ptr 
noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
+; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 
99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
+; CGSCC-NEXT:    call void (ptr, ptr, ptr, ...) @t3_callback_broker(ptr 
noundef null, ptr noalias noundef nonnull align 128 captures(none) 
dereferenceable(4) [[PTR]], ptr noundef nonnull captures(none) 
@t3_callback_callee, ptr noalias align 256 captures(none) [[A]], i64 noundef 
99, ptr noalias nofree noundef nonnull readonly align 64 captures(none) 
dereferenceable(8) [[C]])
 ; CGSCC-NEXT:    ret void
 ;
 entry:
@@ -277,7 +277,7 @@ define internal void @t3_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; TUNIT-LABEL: define {{[^@]+}}@t3_callback_callee
 ; TUNIT-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) {
 ; TUNIT-NEXT:  entry:
-; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; TUNIT-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; TUNIT-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
 ; TUNIT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
 ; TUNIT-NEXT:    tail call void @t3_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr align 32 captures(none) [[TMP0]])
@@ -286,9 +286,9 @@ define internal void @t3_callback_callee(ptr %is_not_null, 
ptr %ptr, ptr %a, i64
 ; CGSCC-LABEL: define {{[^@]+}}@t3_callback_callee
 ; CGSCC-SAME: (ptr nofree noundef nonnull writeonly align 4 captures(none) 
dereferenceable(4) [[IS_NOT_NULL:%.*]], ptr nofree noundef nonnull readonly 
align 8 captures(none) dereferenceable(4) [[PTR:%.*]], ptr align 256 
captures(none) [[A:%.*]], i64 [[B:%.*]], ptr noalias nofree noundef nonnull 
readonly align 64 captures(none) dereferenceable(8) [[C:%.*]]) {
 ; CGSCC-NEXT:  entry:
-; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8
+; CGSCC-NEXT:    [[PTR_VAL:%.*]] = load i32, ptr [[PTR]], align 8, 
!invariant.load [[META0]]
 ; CGSCC-NEXT:    store i32 [[PTR_VAL]], ptr [[IS_NOT_NULL]], align 4
-; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64
+; CGSCC-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[C]], align 64, !invariant.load 
[[META0]]
 ; CGSCC-NEXT:    tail call void @t3_check(ptr align 256 captures(none) [[A]], 
i64 noundef 99, ptr captures(none) [[TMP0]])
 ; CGSCC-NEXT:    ret void
 ;
@@ -311,11 +311,13 @@ declare !callback !0 void @t3_callback_broker(ptr 
nocapture , ptr nocapture , pt
 ;.
 ; CGSCC: attributes #[[ATTR0]] = { nosync }
 ;.
-; TUNIT: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]}
-; TUNIT: [[META1]] = !{i64 2, i64 -1, i64 -1, i1 true}
+; TUNIT: [[META0]] = !{}
+; TUNIT: [[META1:![0-9]+]] = !{[[META2:![0-9]+]]}
+; TUNIT: [[META2]] = !{i64 2, i64 -1, i64 -1, i1 true}
 ;.
-; CGSCC: [[META0:![0-9]+]] = !{[[META1:![0-9]+]]}
-; CGSCC: [[META1]] = !{i64 2, i64 -1, i64 -1, i1 true}
+; CGSCC: [[META0]] = !{}
+; CGSCC: [[META1:![0-9]+]] = !{[[META2:![0-9]+]]}
+; CGSCC: [[META2]] = !{i64 2, i64 -1, i64 -1, i1 true}
 ;.
 ;; NOTE: These prefixes are unused and the list is autogenerated. Do not add 
tests below this line:
 ; CHECK: {{.*}}

>From 3a41cf401fe5d2e605cb72e057db5586b4cbf446 Mon Sep 17 00:00:00 2001
From: Tomas Matheson <[email protected]>
Date: Mon, 24 Aug 2026 11:07:25 +0100
Subject: [PATCH 2/4] [OpenMP] Seed noalias for host callback captures

The generic Attributor can derive noalias and the related lifetime facts for 
callback-mapped capture arguments, but OpenMPOpt does not seed AANoAlias in its 
restricted host run. Consequently capture-container loads remain in loops under 
dereferenceable-at-point semantics.

Seed AANoAlias for pointer arguments mapped to broker operands by callback 
metadata. Existing call-site reasoning checks every callback and direct call 
site and rejects escaped or aliased slots. LICM can then hoist immutable 
capture-container loads without changing the callback ABI or adding an 
OpenMP-specific hoisting transform.

Add LLVM, Clang, and Flang coverage for pointer, scalar, and aggregate captures 
together with escaped, duplicated, unmapped, and unknown-use negatives.
---
 clang/test/OpenMP/bug54082.c                  |  20 +-
 .../test/OpenMP/callback_capture_lifetime.cpp |  40 +++
 .../OpenMP/callback-capture-lifetime.f90      |  48 ++++
 llvm/lib/Transforms/IPO/OpenMPOpt.cpp         |  22 +-
 .../OpenMP/callback-capture-lifetime.ll       | 262 ++++++++++++++++++
 .../Transforms/OpenMP/parallel_deletion.ll    |  20 +-
 .../OpenMP/parallel_region_merging.ll         |  12 +-
 7 files changed, 395 insertions(+), 29 deletions(-)
 create mode 100644 clang/test/OpenMP/callback_capture_lifetime.cpp
 create mode 100644 flang/test/Integration/OpenMP/callback-capture-lifetime.f90
 create mode 100644 llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll

diff --git a/clang/test/OpenMP/bug54082.c b/clang/test/OpenMP/bug54082.c
index 6b8c93e9ffc96..b317a88d8aacb 100644
--- a/clang/test/OpenMP/bug54082.c
+++ b/clang/test/OpenMP/bug54082.c
@@ -69,43 +69,41 @@ void foo() {
 // CHECK-NEXT:    [[X_TRAITS:%.*]] = alloca [1 x 
[[STRUCT_OMP_ALLOCTRAIT_T:%.*]]], align 16
 // CHECK-NEXT:    [[X_ALLOC:%.*]] = alloca i64, align 8
 // CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[X_TRAITS]]) 
#[[ATTR4:[0-9]+]]
-// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 
16 dereferenceable(16) [[X_TRAITS]], ptr noundef nonnull align 16 
dereferenceable(16) @__const.foo.x_traits, i64 16, i1 false)
+// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull 
writeonly align 16 dereferenceable(16) [[X_TRAITS]], ptr noundef nonnull 
readonly align 16 dereferenceable(16) @__const.foo.x_traits, i64 16, i1 false)
 // CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[X_ALLOC]]) 
#[[ATTR4]]
 // CHECK-NEXT:    [[CALL:%.*]] = call i64 @omp_init_allocator(i64 noundef 0, 
i32 noundef 1, ptr noundef nonnull [[X_TRAITS]]) #[[ATTR4]]
 // CHECK-NEXT:    store i64 [[CALL]], ptr [[X_ALLOC]], align 8, !tbaa 
[[LONG_TBAA7:![0-9]+]]
-// CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr nonnull 
@[[GLOB2:[0-9]+]], i32 1, ptr nonnull @foo.omp_outlined, ptr nonnull 
[[X_ALLOC]])
+// CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr nonnull 
@[[GLOB2:[0-9]+]], i32 1, ptr nonnull @foo.omp_outlined, ptr noalias nonnull 
readonly captures(none) [[X_ALLOC]])
 // CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[X_ALLOC]]) 
#[[ATTR4]]
 // CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[X_TRAITS]]) 
#[[ATTR4]]
 // CHECK-NEXT:    ret void
 //
 //
 // CHECK-LABEL: define internal void @foo.omp_outlined(
-// CHECK-SAME: ptr noalias nofree noundef readonly captures(none) 
[[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) 
[[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull readonly align 8 
captures(none) dereferenceable(8) [[X_ALLOC:%.*]]) #[[ATTR3:[0-9]+]] {
+// CHECK-SAME: ptr noalias nofree noundef readonly captures(none) 
[[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) 
[[DOTBOUND_TID_:%.*]], ptr noalias noundef nonnull readonly align 8 
captures(none) dereferenceable(8) [[X_ALLOC:%.*]]) #[[ATTR3:[0-9]+]] {
 // CHECK-NEXT:  [[ENTRY:.*:]]
 // CHECK-NEXT:    [[DOTOMP_LB:%.*]] = alloca i32, align 4
 // CHECK-NEXT:    [[DOTOMP_UB:%.*]] = alloca i32, align 4
 // CHECK-NEXT:    [[DOTOMP_STRIDE:%.*]] = alloca i32, align 4
 // CHECK-NEXT:    [[DOTOMP_IS_LAST:%.*]] = alloca i32, align 4
-// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_LB]]) 
#[[ATTR4]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_LB]]) 
#[[ATTR6:[0-9]+]]
 // CHECK-NEXT:    store i32 0, ptr [[DOTOMP_LB]], align 4, !tbaa 
[[INT_TBAA9:![0-9]+]]
-// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_UB]]) 
#[[ATTR4]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[DOTOMP_UB]]) 
#[[ATTR6]]
 // CHECK-NEXT:    store i32 1023, ptr [[DOTOMP_UB]], align 4, !tbaa 
[[INT_TBAA9]]
-// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull 
[[DOTOMP_STRIDE]]) #[[ATTR4]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull 
[[DOTOMP_STRIDE]]) #[[ATTR6]]
 // CHECK-NEXT:    store i32 1, ptr [[DOTOMP_STRIDE]], align 4, !tbaa 
[[INT_TBAA9]]
-// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull 
[[DOTOMP_IS_LAST]]) #[[ATTR4]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull 
[[DOTOMP_IS_LAST]]) #[[ATTR6]]
 // CHECK-NEXT:    store i32 0, ptr [[DOTOMP_IS_LAST]], align 4, !tbaa 
[[INT_TBAA9]]
 // CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!tbaa [[INT_TBAA9]]
 // CHECK-NEXT:    [[TMP1:%.*]] = load i64, ptr [[X_ALLOC]], align 8, !tbaa 
[[LONG_TBAA7]]
 // CHECK-NEXT:    [[CONV:%.*]] = inttoptr i64 [[TMP1]] to ptr
-// CHECK-NEXT:    [[DOTX__VOID_ADDR:%.*]] = tail call ptr @__kmpc_alloc(i32 
[[TMP0]], i64 8, ptr [[CONV]])
+// CHECK-NEXT:    [[DOTX__VOID_ADDR:%.*]] = tail call ptr @__kmpc_alloc(i32 
[[TMP0]], i64 8, ptr [[CONV]]) #[[ATTR4]]
 // CHECK-NEXT:    call void @__kmpc_for_static_init_4(ptr nonnull 
@[[GLOB1:[0-9]+]], i32 [[TMP0]], i32 34, ptr nonnull [[DOTOMP_IS_LAST]], ptr 
nonnull [[DOTOMP_LB]], ptr nonnull [[DOTOMP_UB]], ptr nonnull 
[[DOTOMP_STRIDE]], i32 1, i32 1)
 // CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[DOTOMP_UB]], align 4, !tbaa 
[[INT_TBAA9]]
 // CHECK-NEXT:    [[COND:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP2]], i32 
1023)
 // CHECK-NEXT:    store i32 [[COND]], ptr [[DOTOMP_UB]], align 4, !tbaa 
[[INT_TBAA9]]
 // CHECK-NEXT:    call void @__kmpc_for_static_fini(ptr nonnull @[[GLOB1]], 
i32 [[TMP0]])
-// CHECK-NEXT:    [[TMP3:%.*]] = load i64, ptr [[X_ALLOC]], align 8, !tbaa 
[[LONG_TBAA7]]
-// CHECK-NEXT:    [[CONV5:%.*]] = inttoptr i64 [[TMP3]] to ptr
-// CHECK-NEXT:    call void @__kmpc_free(i32 [[TMP0]], ptr 
[[DOTX__VOID_ADDR]], ptr [[CONV5]])
+// CHECK-NEXT:    call void @__kmpc_free(i32 [[TMP0]], ptr 
[[DOTX__VOID_ADDR]], ptr [[CONV]])
 // CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull 
[[DOTOMP_IS_LAST]]) #[[ATTR4]]
 // CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull 
[[DOTOMP_STRIDE]]) #[[ATTR4]]
 // CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[DOTOMP_UB]]) 
#[[ATTR4]]
diff --git a/clang/test/OpenMP/callback_capture_lifetime.cpp 
b/clang/test/OpenMP/callback_capture_lifetime.cpp
new file mode 100644
index 0000000000000..f0de3bcf253f6
--- /dev/null
+++ b/clang/test/OpenMP/callback_capture_lifetime.cpp
@@ -0,0 +1,40 @@
+// Verify that OpenMP callback analysis proves the captured pointer containers
+// noalias, allowing their loads to be hoisted out of the loop across an
+// unrelated opaque call.
+//
+// RUN: %clang_cc1 -O2 -ffast-math -fopenmp -x c++ -emit-llvm -o - %s \
+// RUN:   | FileCheck %s
+
+extern "C" int omp_get_num_threads();
+extern "C" int omp_get_thread_num();
+extern "C" void opaque();
+
+double sum_if(double *values, const bool *enabled, int count) {
+  double sum = 0.0;
+#pragma omp parallel reduction(+ : sum)
+  {
+    int threads = omp_get_num_threads();
+    int thread = omp_get_thread_num();
+
+    for (int i = thread * count / threads;
+         i < (thread + 1) * count / threads; ++i) {
+      opaque();
+      if (enabled[i])
+        sum += values[i];
+    }
+  }
+  return sum;
+}
+
+// CHECK-LABEL: define internal void @_Z6sum_ifPdPKbi.omp_outlined(
+// CHECK-SAME: ptr noalias noundef nonnull readonly align 8 captures(none) 
dereferenceable(8) %enabled,
+// CHECK-SAME: {{.*}}ptr noalias noundef nonnull readonly align 8 
captures(none) dereferenceable(8) %values)
+// CHECK: call i32 @omp_get_num_threads()
+// CHECK: call i32 @omp_get_thread_num()
+// CHECK: for.body.lr.ph:
+// CHECK: [[VALUES:%.*]] = load ptr, ptr %values
+// CHECK: for.body:
+// CHECK-NOT: load ptr, ptr %values
+// CHECK: call void @opaque()
+// CHECK: if.then:
+// CHECK: getelementptr {{.*}}, ptr [[VALUES]],
diff --git a/flang/test/Integration/OpenMP/callback-capture-lifetime.f90 
b/flang/test/Integration/OpenMP/callback-capture-lifetime.f90
new file mode 100644
index 0000000000000..742cc8ca7f765
--- /dev/null
+++ b/flang/test/Integration/OpenMP/callback-capture-lifetime.f90
@@ -0,0 +1,48 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+! Verify that host OpenMP callback analysis makes Flang's aggregate capture
+! container noalias, allowing its fields to be loaded before an opaque call.
+!
+! RUN: %flang_fc1 -O2 -fopenmp -emit-llvm %s -o - | FileCheck %s
+
+subroutine capture_lifetime(input, enabled)
+  integer(8), intent(in) :: input
+  logical(1), intent(in) :: enabled(64)
+  integer(8) :: captured
+  integer :: i
+
+  interface
+    subroutine opaque()
+    end subroutine
+    subroutine use_value(value)
+      integer(8), value :: value
+    end subroutine
+  end interface
+
+  captured = input
+  !$omp parallel shared(captured, enabled) private(i)
+  call opaque()
+  do i = 1, 64
+    if (enabled(i)) call use_value(captured)
+  end do
+  !$omp end parallel
+end subroutine
+
+! CHECK-LABEL: define internal void @capture_lifetime_..omp_par(
+! CHECK-SAME: ptr noalias readonly captures(none) [[CAPTURES:%.*]])
+! CHECK: omp.par.entry:
+! CHECK-NEXT: [[ENABLED:%.*]] = load ptr, ptr [[CAPTURES]], align 8
+! CHECK-NEXT: [[CAPTURE_FIELD:%.*]] = getelementptr i8, ptr [[CAPTURES]], i64 8
+! CHECK-NEXT: [[CAPTURED:%.*]] = load ptr, ptr [[CAPTURE_FIELD]], align 8
+! CHECK-NEXT: tail call void @opaque_()
+! CHECK: omp.par.region3:
+! CHECK-NOT: load ptr, ptr [[CAPTURE_FIELD]]
+! CHECK: omp.par.region4:
+! CHECK-NOT: load ptr, ptr [[CAPTURE_FIELD]]
+! CHECK: load i64, ptr [[CAPTURED]], align 8
diff --git a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp 
b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
index 22a5efb402504..c376597fdef9a 100644
--- a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
+++ b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
@@ -5627,10 +5627,28 @@ void OpenMPOpt::registerAAs(bool IsModulePass) {
     }
   }
 
+  // Seed the generic noalias deduction for callback-mapped pointer arguments
+  // in host regions. It will inspect every callback and direct call site 
before
+  // manifesting the attribute.
+  if (!isOpenMPDevice(M)) {
+    for (Function *F : SCC) {
+      if (F->isDeclaration())
+        continue;
+      for (Use &U : F->uses()) {
+        AbstractCallSite ACS(&U);
+        if (!ACS || !ACS.isCallbackCall())
+          continue;
+        for (Argument &Arg : F->args())
+          if (Arg.getType()->isPointerTy() &&
+              ACS.getCallArgOperandNo(Arg) >= 0)
+            A.getOrCreateAAFor<AANoAlias>(IRPosition::argument(Arg));
+      }
+    }
+    return;
+  }
+
   // Create an ExecutionDomain AA for every function and a HeapToStack AA for
   // every function if there is a device kernel.
-  if (!isOpenMPDevice(M))
-    return;
 
   for (auto *F : SCC) {
     if (F->isDeclaration())
diff --git a/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll 
b/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll
new file mode 100644
index 0000000000000..a646e58eb0918
--- /dev/null
+++ b/llvm/test/Transforms/OpenMP/callback-capture-lifetime.ll
@@ -0,0 +1,262 @@
+; RUN: opt -passes=attributor -S < %s | FileCheck %s --check-prefix=ATTRIBUTOR
+; RUN: opt -passes=openmp-opt -S < %s | FileCheck %s --check-prefix=OPENMP
+; RUN: opt -passes='openmp-opt,function(loop-mssa(licm))' -S < %s | FileCheck 
%s --check-prefix=LICM
+; RUN: opt -use-dereferenceable-at-point-semantics=false 
-passes='openmp-opt,function(loop-mssa(licm))' -S < %s | FileCheck %s 
--check-prefix=LICM
+
+target triple = "aarch64-unknown-linux-gnu"
+
+define void @run(ptr %values) {
+entry:
+  %values.addr = alloca ptr, align 8
+  store ptr %values, ptr %values.addr, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined, ptr %values.addr)
+  ret void
+}
+
+; Full Attributor already derives the lifetime attributes for mapped callback
+; arguments. OpenMPOpt should seed the same generic reasoning.
+; ATTRIBUTOR-LABEL: define internal void @outlined(
+; ATTRIBUTOR-SAME: {{.*}}ptr noalias nofree noundef nonnull readonly align 8 
captures(none) dereferenceable(8) %values.capture) {
+; OPENMP-LABEL: define internal void @outlined(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %values.capture) {
+
+; Once noalias and readonly establish that the capture storage remains live,
+; LICM can speculate the load out of the conditional loop even though an
+; unknown call precedes it.
+; LICM-LABEL: define internal void @outlined(
+; LICM-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %values.capture) {
+; LICM: call void @opaque()
+; LICM-NEXT: [[VALUES:%.*]] = load ptr, ptr %values.capture, align 8
+; LICM: loop:
+; LICM-NOT: load ptr, ptr %values.capture
+; LICM: if.then:
+; LICM-NEXT: [[ELEMENT:%.*]] = getelementptr double, ptr [[VALUES]], i64 
[[I:%.*]]
+
+define internal void @outlined(ptr noalias %global_tid, ptr noalias %bound_tid,
+                              ptr align 8 dereferenceable(8) %values.capture) {
+entry:
+  %threads = call i32 @omp_get_num_threads()
+  %thread = call i32 @omp_get_thread_num()
+  call void @opaque()
+  br label %loop
+
+loop:
+  %i = phi i64 [ 0, %entry ], [ %next, %latch ]
+  %enabled = icmp eq i64 %i, 7
+  br i1 %enabled, label %if.then, label %latch
+
+if.then:
+  %values = load ptr, ptr %values.capture, align 8
+  %element = getelementptr double, ptr %values, i64 %i
+  %value = load double, ptr %element, align 8
+  call void @use(double %value, i32 %threads, i32 %thread)
+  br label %latch
+
+latch:
+  %next = add nuw nsw i64 %i, 1
+  %done = icmp eq i64 %next, 64
+  br i1 %done, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+; Scalar and aggregate capture storage use the same pointer-argument proof.
+; OPENMP-LABEL: define internal void @outlined.scalar(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %capture)
+define void @run_scalar(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined.scalar, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.scalar(ptr %gtid, ptr %btid,
+                                      ptr align 8 dereferenceable(8) %capture) 
{
+  %value = load i64, ptr %capture, align 8
+  call void @use.i64(i64 %value)
+  ret void
+}
+
+; OPENMP-LABEL: define internal void @outlined.aggregate(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(16) %capture)
+define void @run_aggregate(ptr %a, ptr %b) {
+  %slot = alloca { ptr, ptr }, align 8
+  store ptr %a, ptr %slot, align 8
+  %second = getelementptr { ptr, ptr }, ptr %slot, i32 0, i32 1
+  store ptr %b, ptr %second, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined.aggregate, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.aggregate(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(16) %capture) {
+  %value = load { ptr, ptr }, ptr %capture, align 8
+  %a = extractvalue { ptr, ptr } %value, 0
+  call void @use.ptr(ptr %a)
+  ret void
+}
+
+; Every callback and direct call site must support the inference.
+; OPENMP-LABEL: define internal void @outlined.serialized(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %capture)
+define void @run_serialized(ptr %values, i32 %condition) {
+  %slot = alloca ptr, align 8
+  store ptr %values, ptr %slot, align 8
+  call void @__kmpc_fork_call_if(ptr null, i32 1, ptr @outlined.serialized,
+                                 i32 %condition, ptr %slot)
+  call void @outlined.serialized(ptr null, ptr null, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.serialized(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+  %value = load ptr, ptr %capture, align 8
+  call void @use.ptr(ptr %value)
+  ret void
+}
+
+; OPENMP-LABEL: define internal void @outlined.multiple(
+; OPENMP-SAME: {{.*}}ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %a.capture, ptr noalias readonly align 8 captures(none) 
dereferenceable(8) %b.capture)
+define void @run_multiple(ptr %a, ptr %b) {
+  %a.slot = alloca ptr, align 8
+  %b.slot = alloca ptr, align 8
+  store ptr %a, ptr %a.slot, align 8
+  store ptr %b, ptr %b.slot, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+      ptr @outlined.multiple, ptr %a.slot, ptr %b.slot)
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+      ptr @outlined.multiple, ptr %a.slot, ptr %b.slot)
+  ret void
+}
+
+define internal void @outlined.multiple(
+    ptr %gtid, ptr %btid,
+    ptr align 8 dereferenceable(8) %a.capture,
+    ptr align 8 dereferenceable(8) %b.capture) {
+  %a = load ptr, ptr %a.capture, align 8
+  %b = load ptr, ptr %b.capture, align 8
+  call void @use.ptr(ptr %a)
+  call void @use.ptr(ptr %b)
+  ret void
+}
+
+; Passing the same storage for two arguments does not satisfy noalias.
+; OPENMP-LABEL: define internal void @outlined.aliased(
+; OPENMP-SAME: {{.*}}ptr writeonly align 8 captures(none) dereferenceable(8) 
%a,
+; OPENMP-SAME: ptr readonly align 8 captures(none) dereferenceable(8) %b)
+define void @run_aliased(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 2,
+      ptr @outlined.aliased, ptr %slot, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.aliased(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %a,
+    ptr align 8 dereferenceable(8) %b) {
+  store i64 0, ptr %a, align 8
+  call void @sync()
+  %bv = load i64, ptr %b, align 8
+  call void @use.i64(i64 %bv)
+  ret void
+}
+
+; Capture storage that escaped before the broker call is not noalias.
+; OPENMP-LABEL: define internal void @outlined.escaped(
+; OPENMP-SAME: {{.*}}ptr readonly align 8 captures(none) dereferenceable(8) 
%capture)
+define void @run_escaped(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void @escape(ptr %slot)
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined.escaped, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.escaped(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+  %value = load i64, ptr %capture, align 8
+  call void @use.i64(i64 %value)
+  ret void
+}
+
+; Missing callback metadata means the argument is not seeded.
+; OPENMP-LABEL: define internal void @outlined.unmapped(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @run_unmapped(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void (ptr, i32, ptr, ...) @fork_without_metadata(ptr null, i32 1,
+      ptr @outlined.unmapped, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.unmapped(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+  %value = load i64, ptr %capture, align 8
+  call void @use.i64(i64 %value)
+  ret void
+}
+
+; An unknown use of the callback prevents all-call-site reasoning.
+@callback_slot = global ptr null
+
+; OPENMP-LABEL: define internal void @outlined.unknown(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @run_unknown(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  store ptr @outlined.unknown, ptr @callback_slot
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined.unknown, ptr %slot)
+  ret void
+}
+
+define internal void @outlined.unknown(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+  %value = load i64, ptr %capture, align 8
+  call void @use.i64(i64 %value)
+  ret void
+}
+
+; Externally visible callbacks do not have a complete set of call sites.
+; OPENMP-LABEL: define void @outlined.external(
+; OPENMP-SAME: {{.*}}ptr align 8 dereferenceable(8) %capture)
+define void @outlined.external(
+    ptr %gtid, ptr %btid, ptr align 8 dereferenceable(8) %capture) {
+  %value = load i64, ptr %capture, align 8
+  call void @use.i64(i64 %value)
+  ret void
+}
+
+define void @run_external(i64 %value) {
+  %slot = alloca i64, align 8
+  store i64 %value, ptr %slot, align 8
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr null, i32 1,
+      ptr @outlined.external, ptr %slot)
+  ret void
+}
+
+declare !callback !0 void @__kmpc_fork_call(ptr, i32, ptr, ...)
+declare !callback !3 void @__kmpc_fork_call_if(ptr, i32, ptr, i32, ptr)
+declare void @fork_without_metadata(ptr, i32, ptr, ...)
+declare i32 @omp_get_num_threads()
+declare i32 @omp_get_thread_num()
+declare void @opaque()
+declare void @sync()
+declare void @use(double, i32, i32) memory(none)
+declare void @use.ptr(ptr) memory(none)
+declare void @use.i64(i64) memory(none)
+declare void @escape(ptr)
+
+!0 = !{!1}
+!1 = !{i64 2, i64 -1, i64 -1, i1 true}
+!llvm.module.flags = !{!2}
+!2 = !{i32 7, !"openmp", i32 51}
+!3 = !{!4}
+!4 = !{i64 2, i64 -1, i64 -1, i64 4, i1 false}
diff --git a/llvm/test/Transforms/OpenMP/parallel_deletion.ll 
b/llvm/test/Transforms/OpenMP/parallel_deletion.ll
index 9d4eb7faa8256..7003f81756ac9 100644
--- a/llvm/test/Transforms/OpenMP/parallel_deletion.ll
+++ b/llvm/test/Transforms/OpenMP/parallel_deletion.ll
@@ -284,10 +284,10 @@ define void @delete_parallel_2() {
 ; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
 ; CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr noundef nonnull align 4 
dereferenceable(4) [[A]]) #[[ATTR18:[0-9]+]]
 ; CHECK-NEXT:    store i32 0, ptr [[A]], align 4
-; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..3, ptr nofree noundef nonnull align 4 captures(none) 
dereferenceable(4) [[A]])
-; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..4, ptr nofree noundef nonnull align 4 captures(none) 
dereferenceable(4) [[A]])
-; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..5, ptr nofree noundef nonnull align 4 captures(none) 
dereferenceable(4) [[A]])
-; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..6, ptr nofree noundef nonnull align 4 captures(none) 
dereferenceable(4) [[A]])
+; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..3, ptr noalias nofree noundef nonnull align 4 
captures(none) dereferenceable(4) [[A]])
+; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..4, ptr noalias nofree noundef nonnull align 4 
captures(none) dereferenceable(4) [[A]])
+; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..5, ptr noalias nofree noundef nonnull align 4 
captures(none) dereferenceable(4) [[A]])
+; CHECK-NEXT:    call void (ptr, i32, ptr, ...) @__kmpc_fork_call(ptr noundef 
nonnull align 8 dereferenceable(24) @[[GLOB0]], i32 noundef 1, ptr noundef 
nonnull @.omp_outlined..6, ptr noalias nofree noundef nonnull align 4 
captures(none) dereferenceable(4) [[A]])
 ; CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr noundef nonnull [[A]])
 ; CHECK-NEXT:    ret void
 ;
@@ -327,7 +327,7 @@ entry:
 
 define internal void @.omp_outlined..3(ptr noalias %.global_tid., ptr noalias 
%.bound_tid., ptr dereferenceable(4) %a) {
 ; CHECK-LABEL: define {{[^@]+}}@.omp_outlined..3
-; CHECK-SAME: (ptr noalias nofree readnone captures(none) 
[[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) 
[[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 4 captures(none) 
dereferenceable(4) [[A:%.*]]) #[[ATTR6:[0-9]+]] {
+; CHECK-SAME: (ptr noalias nofree readnone captures(none) 
[[DOTGLOBAL_TID_:%.*]], ptr noalias nofree readnone captures(none) 
[[DOTBOUND_TID_:%.*]], ptr noalias nofree noundef nonnull align 4 
captures(none) dereferenceable(4) [[A:%.*]]) #[[ATTR6:[0-9]+]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[CALL:%.*]] = call i32 @omp_get_thread_num() 
#[[ATTR19:[0-9]+]]
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[CALL]], 0
@@ -386,7 +386,7 @@ define internal void @.omp_outlined..4(ptr noalias 
%.global_tid., ptr noalias %.
 ; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 
captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree 
readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 
4 captures(none) dereferenceable(4) [[A:%.*]]) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[TMP:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META1:![0-9]+]]
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @__kmpc_master(ptr noundef nonnull 
@[[GLOB0]], i32 [[TMP]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @__kmpc_master(ptr noundef nonnull 
@[[GLOB0]], i32 [[TMP]]) #[[ATTR9:[0-9]+]]
 ; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[TMP1]], 0
 ; CHECK-NEXT:    br i1 [[TMP2]], label [[OMP_IF_END:%.*]], label 
[[OMP_IF_THEN:%.*]]
 ; CHECK:       omp_if.then:
@@ -459,7 +459,7 @@ define internal void @.omp_outlined..5(ptr noalias 
%.global_tid., ptr noalias %.
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[OMP_GLOBAL_THREAD_NUM:%.*]] = call i32 
@__kmpc_global_thread_num(ptr noundef nonnull @[[GLOB0]]) #[[ATTR19]]
 ; CHECK-NEXT:    [[TMP:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META1]]
-; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @__kmpc_single(ptr noundef nonnull 
@[[GLOB0]], i32 [[TMP]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @__kmpc_single(ptr noundef nonnull 
@[[GLOB0]], i32 [[TMP]]) #[[ATTR9]]
 ; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[TMP1]], 0
 ; CHECK-NEXT:    br i1 [[TMP2]], label [[OMP_IF_END:%.*]], label 
[[OMP_IF_THEN:%.*]]
 ; CHECK:       omp_if.then:
@@ -527,7 +527,7 @@ omp_if.end:                                       ; preds = 
%entry, %omp_if.then
 
 define internal void @.omp_outlined..6(ptr noalias %.global_tid., ptr noalias 
%.bound_tid., ptr dereferenceable(4) %a) {
 ; CHECK-LABEL: define {{[^@]+}}@.omp_outlined..6
-; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 
captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree 
readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 
4 captures(none) dereferenceable(4) [[A:%.*]]) {
+; CHECK-SAME: (ptr noalias nofree noundef nonnull readonly align 4 
captures(none) dereferenceable(4) [[DOTGLOBAL_TID_:%.*]], ptr noalias nofree 
readnone captures(none) [[DOTBOUND_TID_:%.*]], ptr nofree noundef nonnull align 
4 captures(none) dereferenceable(4) [[A:%.*]]) #[[ATTR9]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[A1:%.*]] = alloca i32, align 4
 ; CHECK-NEXT:    [[DOTOMP_REDUCTION_RED_LIST:%.*]] = alloca [1 x ptr], align 8
@@ -535,7 +535,7 @@ define internal void @.omp_outlined..6(ptr noalias 
%.global_tid., ptr noalias %.
 ; CHECK-NEXT:    store i32 1, ptr [[A1]], align 4
 ; CHECK-NEXT:    store ptr [[A1]], ptr [[DOTOMP_REDUCTION_RED_LIST]], align 8
 ; CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META1]]
-; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @__kmpc_reduce_nowait(ptr noundef 
nonnull @[[GLOB2:[0-9]+]], i32 [[TMP2]], i32 noundef 1, i64 noundef 8, ptr 
noundef nonnull align 8 [[DOTOMP_REDUCTION_RED_LIST]], ptr noundef nonnull 
@.omp.reduction.reduction_func, ptr noundef nonnull 
@.gomp_critical_user_.reduction.var)
+; CHECK-NEXT:    [[TMP4:%.*]] = call i32 @__kmpc_reduce_nowait(ptr noundef 
nonnull @[[GLOB2:[0-9]+]], i32 [[TMP2]], i32 noundef 1, i64 noundef 8, ptr 
noundef nonnull align 8 [[DOTOMP_REDUCTION_RED_LIST]], ptr noundef nonnull 
@.omp.reduction.reduction_func, ptr noundef nonnull 
@.gomp_critical_user_.reduction.var) #[[ATTR9]]
 ; CHECK-NEXT:    switch i32 [[TMP4]], label [[DOTOMP_REDUCTION_DEFAULT:%.*]] [
 ; CHECK-NEXT:      i32 1, label [[DOTOMP_REDUCTION_CASE1:%.*]]
 ; CHECK-NEXT:      i32 2, label [[DOTOMP_REDUCTION_CASE2:%.*]]
@@ -545,7 +545,7 @@ define internal void @.omp_outlined..6(ptr noalias 
%.global_tid., ptr noalias %.
 ; CHECK-NEXT:    [[TMP6:%.*]] = load i32, ptr [[A1]], align 4
 ; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[TMP5]], [[TMP6]]
 ; CHECK-NEXT:    store i32 [[ADD]], ptr [[A]], align 4
-; CHECK-NEXT:    call void @__kmpc_end_reduce_nowait(ptr noundef nonnull 
@[[GLOB2]], i32 [[TMP2]], ptr noundef nonnull 
@.gomp_critical_user_.reduction.var)
+; CHECK-NEXT:    call void @__kmpc_end_reduce_nowait(ptr noundef nonnull 
@[[GLOB2]], i32 [[TMP2]], ptr noundef nonnull 
@.gomp_critical_user_.reduction.var) #[[ATTR9]]
 ; CHECK-NEXT:    br label [[DOTOMP_REDUCTION_DEFAULT]]
 ; CHECK:       .omp.reduction.case2:
 ; CHECK-NEXT:    [[TMP7:%.*]] = load i32, ptr [[A1]], align 4
diff --git a/llvm/test/Transforms/OpenMP/parallel_region_merging.ll 
b/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
index 6d3e5935d2e11..312a4ec8be74b 100644
--- a/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
+++ b/llvm/test/Transforms/OpenMP/parallel_region_merging.ll
@@ -5252,12 +5252,12 @@ entry:
 ; CHECK2:       omp.par.pre_finalize:
 ; CHECK2-NEXT:    br label [[DOTFINI:%.*]]
 ; CHECK2:       .fini:
-; CHECK2-NEXT:    call void @llvm.lifetime.end.p0(ptr noundef nonnull [[B]])
+; CHECK2-NEXT:    call void @llvm.lifetime.end.p0(ptr noundef nonnull 
captures(none) [[B]])
 ; CHECK2-NEXT:    br label [[OMP_PAR_EXIT_EXITSTUB:%.*]]
 ; CHECK2:       omp_region.body:
 ; CHECK2-NEXT:    br label [[SEQ_PAR_MERGED:%.*]]
 ; CHECK2:       seq.par.merged:
-; CHECK2-NEXT:    call void @llvm.lifetime.start.p0(ptr noundef nonnull align 
4 dereferenceable(4) [[B]])
+; CHECK2-NEXT:    call void @llvm.lifetime.start.p0(ptr noundef nonnull align 
4 captures(none) dereferenceable(4) [[B]])
 ; CHECK2-NEXT:    [[ADD:%.*]] = add nsw i32 [[TMP2]], 1
 ; CHECK2-NEXT:    store i32 [[ADD]], ptr [[B]], align 4
 ; CHECK2-NEXT:    br label [[OMP_PAR_MERGED_SPLIT:%.*]]
@@ -5352,7 +5352,7 @@ entry:
 ; CHECK2-NEXT:    br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label 
[[IF_THEN:%.*]]
 ; CHECK2:       if.then:
 ; CHECK2-NEXT:    [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META7:![0-9]+]]
-; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
 ; CHECK2-NEXT:    ret void
 ; CHECK2:       if.end:
 ; CHECK2-NEXT:    ret void
@@ -5366,7 +5366,7 @@ entry:
 ; CHECK2-NEXT:    br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label 
[[IF_THEN:%.*]]
 ; CHECK2:       if.then:
 ; CHECK2-NEXT:    [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META7]]
-; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
 ; CHECK2-NEXT:    ret void
 ; CHECK2:       if.end:
 ; CHECK2-NEXT:    ret void
@@ -5464,7 +5464,7 @@ entry:
 ; CHECK2-NEXT:    br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label 
[[IF_THEN:%.*]]
 ; CHECK2:       if.then:
 ; CHECK2-NEXT:    [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META7]]
-; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
 ; CHECK2-NEXT:    ret void
 ; CHECK2:       if.end:
 ; CHECK2-NEXT:    ret void
@@ -5478,7 +5478,7 @@ entry:
 ; CHECK2-NEXT:    br i1 [[TOBOOL_NOT]], label [[IF_END:%.*]], label 
[[IF_THEN:%.*]]
 ; CHECK2:       if.then:
 ; CHECK2-NEXT:    [[TMP1:%.*]] = load i32, ptr [[DOTGLOBAL_TID_]], align 4, 
!invariant.load [[META7]]
-; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1)
+; CHECK2-NEXT:    [[TMP2:%.*]] = call i32 @__kmpc_cancel(ptr noundef nonnull 
@[[GLOB1]], i32 [[TMP1]], i32 noundef 1) #[[ATTR0]]
 ; CHECK2-NEXT:    ret void
 ; CHECK2:       if.end:
 ; CHECK2-NEXT:    ret void

>From e31cb765d68cbe7e1a5c144fd5979f5efea6012c Mon Sep 17 00:00:00 2001
From: Tomas Matheson <[email protected]>
Date: Mon, 24 Aug 2026 11:07:35 +0100
Subject: [PATCH 3/4] [Transforms] Mark posix_memalign output slot nocapture

posix_memalign writes the allocated pointer through its first argument but does 
not retain the address of that output slot. Without captures(none), capture 
tracking conservatively treats an alloca passed to posix_memalign as escaped 
and Attributor cannot prove it noalias at a later callback call site.

Model the POSIX contract in inferNonMandatoryLibFuncAttrs. Test both the 
inferred declaration attribute and the OpenMP callback-lifetime case that 
exposed the missing model.
---
 llvm/lib/Transforms/Utils/BuildLibCalls.cpp   |  3 +
 .../Transforms/InferFunctionAttrs/annotate.ll |  2 +-
 .../OpenMP/callback-capture-posix-memalign.ll | 63 +++++++++++++++++++
 3 files changed, 67 insertions(+), 1 deletion(-)
 create mode 100644 
llvm/test/Transforms/OpenMP/callback-capture-posix-memalign.ll

diff --git a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp 
b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
index 0afcc9df40070..c3621d56ec453 100644
--- a/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/BuildLibCalls.cpp
@@ -534,6 +534,9 @@ bool llvm::inferNonMandatoryLibFuncAttrs(Function &F,
     Changed |= setDoesNotCapture(F, 0);
     Changed |= setOnlyReadsMemory(F, 0);
     break;
+  case LibFunc_posix_memalign:
+    Changed |= setDoesNotCapture(F, 0);
+    break;
   case LibFunc_aligned_alloc:
     Changed |= setAlignedAllocParam(F, 0);
     Changed |= setAllocSize(F, 1, std::nullopt);
diff --git a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll 
b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
index 44b76640cf97f..298b4af8d77c5 100644
--- a/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
+++ b/llvm/test/Transforms/InferFunctionAttrs/annotate.ll
@@ -817,7 +817,7 @@ declare void @perror(ptr)
 ; CHECK: declare noalias noundef ptr @popen(ptr noundef readonly 
captures(none), ptr noundef readonly captures(none)) [[NOFREE_NOUNWIND]]
 declare ptr @popen(ptr, ptr)
 
-; CHECK: declare i32 @posix_memalign(ptr, i64, i64) [[NOFREE]]
+; CHECK: declare i32 @posix_memalign(ptr captures(none), i64, i64) [[NOFREE]]
 declare i32 @posix_memalign(ptr, i64, i64)
 
 ; CHECK: declare double @pow(double, double) 
[[ERRNOMEMONLY_NOFREE_NOSYNC_NOUNWIND_WILLRETURN]]
diff --git a/llvm/test/Transforms/OpenMP/callback-capture-posix-memalign.ll 
b/llvm/test/Transforms/OpenMP/callback-capture-posix-memalign.ll
new file mode 100644
index 0000000000000..6a9c24ece5c08
--- /dev/null
+++ b/llvm/test/Transforms/OpenMP/callback-capture-posix-memalign.ll
@@ -0,0 +1,63 @@
+; RUN: opt -passes='inferattrs,openmp-opt,function(loop-mssa(licm))' -S < %s | 
FileCheck %s
+
+target triple = "aarch64-unknown-linux-gnu"
+
+; posix_memalign writes through its output slot but does not capture the slot.
+; That earlier use must not prevent the callback capture from becoming noalias
+; or the load from being hoisted across an unknown call.
+; CHECK: call i32 @posix_memalign(ptr captures(none) %slot, i64 64, i64 %size)
+; CHECK-LABEL: define internal void @outlined(
+; CHECK-SAME: ptr noalias readonly align 8 captures(none) dereferenceable(8) 
%capture)
+; CHECK: call void @opaque()
+; CHECK-NEXT: [[VALUES:%.*]] = load ptr, ptr %capture, align 8
+; CHECK: loop:
+; CHECK-NOT: load ptr, ptr %capture
+; CHECK: if.then:
+; CHECK-NEXT: [[ELEMENT:%.*]] = getelementptr double, ptr [[VALUES]], i64 
[[I:%.*]]
+
+define i32 @run(i64 %size) {
+entry:
+  %slot = alloca ptr, align 8
+  %status = call i32 @posix_memalign(ptr %slot, i64 64, i64 %size)
+  call void (ptr, i32, ptr, ...) @__kmpc_fork_call(
+      ptr null, i32 1, ptr @outlined, ptr %slot)
+  ret i32 %status
+}
+
+define internal void @outlined(
+    ptr %global_tid, ptr %bound_tid,
+    ptr align 8 dereferenceable(8) %capture) {
+entry:
+  call void @opaque()
+  br label %loop
+
+loop:
+  %i = phi i64 [ 0, %entry ], [ %next, %latch ]
+  %enabled = icmp eq i64 %i, 7
+  br i1 %enabled, label %if.then, label %latch
+
+if.then:
+  %values = load ptr, ptr %capture, align 8
+  %element = getelementptr double, ptr %values, i64 %i
+  %value = load double, ptr %element, align 8
+  call void @use(double %value)
+  br label %latch
+
+latch:
+  %next = add nuw nsw i64 %i, 1
+  %done = icmp eq i64 %next, 64
+  br i1 %done, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+declare i32 @posix_memalign(ptr, i64, i64)
+declare !callback !0 void @__kmpc_fork_call(ptr, i32, ptr, ...)
+declare void @opaque()
+declare void @use(double) memory(none)
+
+!0 = !{!1}
+!1 = !{i64 2, i64 -1, i64 -1, i1 true}
+!llvm.module.flags = !{!2}
+!2 = !{i32 7, !"openmp", i32 51}

>From 6d5e3d8c4d85bd9ba12e550f54b19641558f0ac8 Mon Sep 17 00:00:00 2001
From: Tomas Matheson <[email protected]>
Date: Tue, 25 Aug 2026 11:07:54 +0100
Subject: [PATCH 4/4] [OpenMP] Fix callback lifetime test on Windows

Pin the test triple so its Itanium-mangled outlined-function check is stable on 
Windows hosts. Apply git clang-format to the OpenMPOpt change.
---
 clang/test/OpenMP/callback_capture_lifetime.cpp | 3 ++-
 llvm/lib/Transforms/IPO/OpenMPOpt.cpp           | 3 +--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/test/OpenMP/callback_capture_lifetime.cpp 
b/clang/test/OpenMP/callback_capture_lifetime.cpp
index f0de3bcf253f6..c4e1195c550c7 100644
--- a/clang/test/OpenMP/callback_capture_lifetime.cpp
+++ b/clang/test/OpenMP/callback_capture_lifetime.cpp
@@ -2,7 +2,8 @@
 // noalias, allowing their loads to be hoisted out of the loop across an
 // unrelated opaque call.
 //
-// RUN: %clang_cc1 -O2 -ffast-math -fopenmp -x c++ -emit-llvm -o - %s \
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -ffast-math -fopenmp \
+// RUN:   -x c++ -emit-llvm -o - %s \
 // RUN:   | FileCheck %s
 
 extern "C" int omp_get_num_threads();
diff --git a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp 
b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
index c376597fdef9a..0fff56cf17ced 100644
--- a/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
+++ b/llvm/lib/Transforms/IPO/OpenMPOpt.cpp
@@ -5639,8 +5639,7 @@ void OpenMPOpt::registerAAs(bool IsModulePass) {
         if (!ACS || !ACS.isCallbackCall())
           continue;
         for (Argument &Arg : F->args())
-          if (Arg.getType()->isPointerTy() &&
-              ACS.getCallArgOperandNo(Arg) >= 0)
+          if (Arg.getType()->isPointerTy() && ACS.getCallArgOperandNo(Arg) >= 
0)
             A.getOrCreateAAFor<AANoAlias>(IRPosition::argument(Arg));
       }
     }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to